/*!
 * UEditor Mini版本
 * version: 1.2.2
 * build: Wed Mar 19 2014 17:14:14 GMT+0800 (中国标准时间)
 */

(function ($) {
    UMEDITOR_CONFIG = window.UMEDITOR_CONFIG || {};

    window.UM = {
        plugins: {},

        commands: {},

        I18N: {},

        version: "1.2.2",
    };

    var dom = (UM.dom = {});
    /**
     * 浏览器判断模块
     * @file
     * @module UE.browser
     * @since 1.2.6.1
     */

    /**
     * 提供浏览器检测的模块
     * @unfile
     * @module UE.browser
     */
    var browser = (UM.browser = (function () {
        var agent = navigator.userAgent.toLowerCase(),
            opera = window.opera,
            browser = {
                /**
                 * @property {boolean} ie 检测当前浏览器是否为IE
                 * @example
                 * ```javascript
                 * if ( UE.browser.ie ) {
                 *     console.log( '当前浏览器是IE' );
                 * }
                 * ```
                 */
                ie: /(msie\s|trident.*rv:)([\w.]+)/.test(agent),

                /**
                 * @property {boolean} opera 检测当前浏览器是否为Opera
                 * @example
                 * ```javascript
                 * if ( UE.browser.opera ) {
                 *     console.log( '当前浏览器是Opera' );
                 * }
                 * ```
                 */
                opera: !!opera && opera.version,

                /**
                 * @property {boolean} webkit 检测当前浏览器是否是webkit内核的浏览器
                 * @example
                 * ```javascript
                 * if ( UE.browser.webkit ) {
                 *     console.log( '当前浏览器是webkit内核浏览器' );
                 * }
                 * ```
                 */
                webkit: agent.indexOf(" applewebkit/") > -1,

                /**
                 * @property {boolean} mac 检测当前浏览器是否是运行在mac平台下
                 * @example
                 * ```javascript
                 * if ( UE.browser.mac ) {
                 *     console.log( '当前浏览器运行在mac平台下' );
                 * }
                 * ```
                 */
                mac: agent.indexOf("macintosh") > -1,

                /**
                 * @property {boolean} quirks 检测当前浏览器是否处于“怪异模式”下
                 * @example
                 * ```javascript
                 * if ( UE.browser.quirks ) {
                 *     console.log( '当前浏览器运行处于“怪异模式”' );
                 * }
                 * ```
                 */
                quirks: document.compatMode == "BackCompat",
            };

        /**
         * @property {boolean} gecko 检测当前浏览器内核是否是gecko内核
         * @example
         * ```javascript
         * if ( UE.browser.gecko ) {
         *     console.log( '当前浏览器内核是gecko内核' );
         * }
         * ```
         */
        browser.gecko = navigator.product == "Gecko" && !browser.webkit && !browser.opera && !browser.ie;

        var version = 0;

        // Internet Explorer 6.0+
        if (browser.ie) {
            var v1 = agent.match(/(?:msie\s([\w.]+))/);
            var v2 = agent.match(/(?:trident.*rv:([\w.]+))/);
            if (v1 && v2 && v1[1] && v2[1]) {
                version = Math.max(v1[1] * 1, v2[1] * 1);
            } else if (v1 && v1[1]) {
                version = v1[1] * 1;
            } else if (v2 && v2[1]) {
                version = v2[1] * 1;
            } else {
                version = 0;
            }

            browser.ie11Compat = document.documentMode == 11;
            /**
             * @property { boolean } ie9Compat 检测浏览器模式是否为 IE9 兼容模式
             * @warning 如果浏览器不是IE， 则该值为undefined
             * @example
             * ```javascript
             * if ( UE.browser.ie9Compat ) {
             *     console.log( '当前浏览器运行在IE9兼容模式下' );
             * }
             * ```
             */
            browser.ie9Compat = document.documentMode == 9;

            /**
             * @property { boolean } ie8 检测浏览器是否是IE8浏览器
             * @warning 如果浏览器不是IE， 则该值为undefined
             * @example
             * ```javascript
             * if ( UE.browser.ie8 ) {
             *     console.log( '当前浏览器是IE8浏览器' );
             * }
             * ```
             */
            browser.ie8 = !!document.documentMode;

            /**
             * @property { boolean } ie8Compat 检测浏览器模式是否为 IE8 兼容模式
             * @warning 如果浏览器不是IE， 则该值为undefined
             * @example
             * ```javascript
             * if ( UE.browser.ie8Compat ) {
             *     console.log( '当前浏览器运行在IE8兼容模式下' );
             * }
             * ```
             */
            browser.ie8Compat = document.documentMode == 8;

            /**
             * @property { boolean } ie7Compat 检测浏览器模式是否为 IE7 兼容模式
             * @warning 如果浏览器不是IE， 则该值为undefined
             * @example
             * ```javascript
             * if ( UE.browser.ie7Compat ) {
             *     console.log( '当前浏览器运行在IE7兼容模式下' );
             * }
             * ```
             */
            browser.ie7Compat = (version == 7 && !document.documentMode) || document.documentMode == 7;

            /**
             * @property { boolean } ie6Compat 检测浏览器模式是否为 IE6 模式 或者怪异模式
             * @warning 如果浏览器不是IE， 则该值为undefined
             * @example
             * ```javascript
             * if ( UE.browser.ie6Compat ) {
             *     console.log( '当前浏览器运行在IE6模式或者怪异模式下' );
             * }
             * ```
             */
            browser.ie6Compat = version < 7 || browser.quirks;

            browser.ie9above = version > 8;

            browser.ie9below = version < 9;
        }

        // Gecko.
        if (browser.gecko) {
            var geckoRelease = agent.match(/rv:([\d\.]+)/);
            if (geckoRelease) {
                geckoRelease = geckoRelease[1].split(".");
                version = geckoRelease[0] * 10000 + (geckoRelease[1] || 0) * 100 + (geckoRelease[2] || 0) * 1;
            }
        }

        /**
         * @property { Number } chrome 检测当前浏览器是否为Chrome, 如果是，则返回Chrome的大版本号
         * @warning 如果浏览器不是chrome， 则该值为undefined
         * @example
         * ```javascript
         * if ( UE.browser.chrome ) {
         *     console.log( '当前浏览器是Chrome' );
         * }
         * ```
         */
        if (/chrome\/(\d+\.\d)/i.test(agent)) {
            browser.chrome = +RegExp["\x241"];
        }

        /**
         * @property { Number } safari 检测当前浏览器是否为Safari, 如果是，则返回Safari的大版本号
         * @warning 如果浏览器不是safari， 则该值为undefined
         * @example
         * ```javascript
         * if ( UE.browser.safari ) {
         *     console.log( '当前浏览器是Safari' );
         * }
         * ```
         */
        if (/(\d+\.\d)?(?:\.\d)?\s+safari\/?(\d+\.\d+)?/i.test(agent) && !/chrome/i.test(agent)) {
            browser.safari = +(RegExp["\x241"] || RegExp["\x242"]);
        }

        // Opera 9.50+
        if (browser.opera) version = parseFloat(opera.version());

        // WebKit 522+ (Safari 3+)
        if (browser.webkit) version = parseFloat(agent.match(/ applewebkit\/(\d+)/)[1]);

        /**
         * @property { Number } version 检测当前浏览器版本号
         * @remind
         * <ul>
         *     <li>IE系列返回值为5,6,7,8,9,10等</li>
         *     <li>gecko系列会返回10900，158900等</li>
         *     <li>webkit系列会返回其build号 (如 522等)</li>
         * </ul>
         * @example
         * ```javascript
         * console.log( '当前浏览器版本号是： ' + UE.browser.version );
         * ```
         */
        browser.version = version;

        /**
         * @property { boolean } isCompatible 检测当前浏览器是否能够与UEditor良好兼容
         * @example
         * ```javascript
         * if ( UE.browser.isCompatible ) {
         *     console.log( '浏览器与UEditor能够良好兼容' );
         * }
         * ```
         */
        browser.isCompatible =
            !browser.mobile &&
            ((browser.ie && version >= 6) ||
                (browser.gecko && version >= 10801) ||
                (browser.opera && version >= 9.5) ||
                (browser.air && version >= 1) ||
                (browser.webkit && version >= 522) ||
                false);
        return browser;
    })());
    //快捷方式
    var ie = browser.ie,
        webkit = browser.webkit,
        gecko = browser.gecko,
        opera = browser.opera;
    /**
     * @file
     * @name UM.Utils
     * @short Utils
     * @desc UEditor封装使用的静态工具函数
     * @import editor.js
     */
    var utils = (UM.utils = {
        /**
         * 遍历数组，对象，nodeList
         * @name each
         * @grammar UM.utils.each(obj,iterator,[context])
         * @since 1.2.4+
         * @desc
         * * obj 要遍历的对象
         * * iterator 遍历的方法,方法的第一个是遍历的值，第二个是索引，第三个是obj
         * * context  iterator的上下文
         * @example
         * UM.utils.each([1,2],function(v,i){
         *     console.log(v)//值
         *     console.log(i)//索引
         * })
         * UM.utils.each(document.getElementsByTagName('*'),function(n){
         *     console.log(n.tagName)
         * })
         */
        each: function (obj, iterator, context) {
            if (obj == null) return;
            if (obj.length === +obj.length) {
                for (var i = 0, l = obj.length; i < l; i++) {
                    if (iterator.call(context, obj[i], i, obj) === false) return false;
                }
            } else {
                for (var key in obj) {
                    if (obj.hasOwnProperty(key)) {
                        if (iterator.call(context, obj[key], key, obj) === false) return false;
                    }
                }
            }
        },

        makeInstance: function (obj) {
            var noop = new Function();
            noop.prototype = obj;
            obj = new noop();
            noop.prototype = null;
            return obj;
        },
        /**
         * 将source对象中的属性扩展到target对象上
         * @name extend
         * @grammar UM.utils.extend(target,source)  => Object  //覆盖扩展
         * @grammar UM.utils.extend(target,source,true)  ==> Object  //保留扩展
         */
        extend: function (t, s, b) {
            if (s) {
                for (var k in s) {
                    if (!b || !t.hasOwnProperty(k)) {
                        t[k] = s[k];
                    }
                }
            }
            return t;
        },
        extend2: function (t) {
            var a = arguments;
            for (var i = 1; i < a.length; i++) {
                var x = a[i];
                for (var k in x) {
                    if (!t.hasOwnProperty(k)) {
                        t[k] = x[k];
                    }
                }
            }
            return t;
        },
        /**
         * 模拟继承机制，subClass继承superClass
         * @name inherits
         * @grammar UM.utils.inherits(subClass,superClass) => subClass
         * @example
         * function SuperClass(){
         *     this.name = "小李";
         * }
         * SuperClass.prototype = {
         *     hello:function(str){
         *         console.log(this.name + str);
         *     }
         * }
         * function SubClass(){
         *     this.name = "小张";
         * }
         * UM.utils.inherits(SubClass,SuperClass);
         * var sub = new SubClass();
         * sub.hello("早上好!"); ==> "小张早上好！"
         */
        inherits: function (subClass, superClass) {
            var oldP = subClass.prototype,
                newP = utils.makeInstance(superClass.prototype);
            utils.extend(newP, oldP, true);
            subClass.prototype = newP;
            return (newP.constructor = subClass);
        },

        /**
         * 用指定的context作为fn上下文，也就是this
         * @name bind
         * @grammar UM.utils.bind(fn,context)  =>  fn
         */
        bind: function (fn, context) {
            return function () {
                return fn.apply(context, arguments);
            };
        },

        /**
         * 创建延迟delay执行的函数fn
         * @name defer
         * @grammar UM.utils.defer(fn,delay)  =>fn   //延迟delay毫秒执行fn，返回fn
         * @grammar UM.utils.defer(fn,delay,exclusion)  =>fn   //延迟delay毫秒执行fn，若exclusion为真，则互斥执行fn
         * @example
         * function test(){
         *     console.log("延迟输出！");
         * }
         * //非互斥延迟执行
         * var testDefer = UM.utils.defer(test,1000);
         * testDefer();   =>  "延迟输出！";
         * testDefer();   =>  "延迟输出！";
         * //互斥延迟执行
         * var testDefer1 = UM.utils.defer(test,1000,true);
         * testDefer1();   =>  //本次不执行
         * testDefer1();   =>  "延迟输出！";
         */
        defer: function (fn, delay, exclusion) {
            var timerID;
            return function () {
                if (exclusion) {
                    clearTimeout(timerID);
                }
                timerID = setTimeout(fn, delay);
            };
        },

        /**
         * 查找元素item在数组array中的索引, 若找不到返回-1
         * @name indexOf
         * @grammar UM.utils.indexOf(array,item)  => index|-1  //默认从数组开头部开始搜索
         * @grammar UM.utils.indexOf(array,item,start)  => index|-1  //start指定开始查找的位置
         */
        indexOf: function (array, item, start) {
            var index = -1;
            start = this.isNumber(start) ? start : 0;
            this.each(array, function (v, i) {
                if (i >= start && v === item) {
                    index = i;
                    return false;
                }
            });
            return index;
        },

        /**
         * 移除数组array中的元素item
         * @name removeItem
         * @grammar UM.utils.removeItem(array,item)
         */
        removeItem: function (array, item) {
            for (var i = 0, l = array.length; i < l; i++) {
                if (array[i] === item) {
                    array.splice(i, 1);
                    i--;
                }
            }
        },

        /**
         * 删除字符串str的首尾空格
         * @name trim
         * @grammar UM.utils.trim(str) => String
         */
        trim: function (str) {
            return str.replace(/(^[ \t\n\r]+)|([ \t\n\r]+$)/g, "");
        },

        /**
         * 将字符串list(以','分隔)或者数组list转成哈希对象
         * @name listToMap
         * @grammar UM.utils.listToMap(list)  => Object  //Object形如{test:1,br:1,textarea:1}
         */
        listToMap: function (list) {
            if (!list) return {};
            list = utils.isArray(list) ? list : list.split(",");
            for (var i = 0, ci, obj = {}; (ci = list[i++]); ) {
                obj[ci.toUpperCase()] = obj[ci] = 1;
            }
            return obj;
        },

        /**
         * 将str中的html符号转义,默认将转义''&<">''四个字符，可自定义reg来确定需要转义的字符
         * @name unhtml
         * @grammar UM.utils.unhtml(str);  => String
         * @grammar UM.utils.unhtml(str,reg)  => String
         * @example
         * var html = '<body>You say:"你好！Baidu & UEditor!"</body>';
         * UM.utils.unhtml(html);   ==>  &lt;body&gt;You say:&quot;你好！Baidu &amp; UEditor!&quot;&lt;/body&gt;
         * UM.utils.unhtml(html,/[<>]/g)  ==>  &lt;body&gt;You say:"你好！Baidu & UEditor!"&lt;/body&gt;
         */
        unhtml: function (str, reg) {
            return str
                ? str.replace(reg || /[&<">'](?:(amp|lt|quot|gt|#39|nbsp);)?/g, function (a, b) {
                      if (b) {
                          return a;
                      } else {
                          return {
                              "<": "&lt;",
                              "&": "&amp;",
                              '"': "&quot;",
                              ">": "&gt;",
                              "'": "&#39;",
                          }[a];
                      }
                  })
                : "";
        },
        /**
         * 将str中的转义字符还原成html字符
         * @name html
         * @grammar UM.utils.html(str)  => String   //详细参见<code><a href = '#unhtml'>unhtml</a></code>
         */
        html: function (str) {
            return str
                ? str.replace(/&((g|l|quo)t|amp|#39);/g, function (m) {
                      return {
                          "&lt;": "<",
                          "&amp;": "&",
                          "&quot;": '"',
                          "&gt;": ">",
                          "&#39;": "'",
                      }[m];
                  })
                : "";
        },
        /**
         * 将css样式转换为驼峰的形式。如font-size => fontSize
         * @name cssStyleToDomStyle
         * @grammar UM.utils.cssStyleToDomStyle(cssName)  => String
         */
        cssStyleToDomStyle: (function () {
            var test = document.createElement("div").style,
                cache = {
                    float: test.cssFloat != undefined ? "cssFloat" : test.styleFloat != undefined ? "styleFloat" : "float",
                };

            return function (cssName) {
                return (
                    cache[cssName] ||
                    (cache[cssName] = cssName.toLowerCase().replace(/-./g, function (match) {
                        return match.charAt(1).toUpperCase();
                    }))
                );
            };
        })(),
        /**
         * 动态加载文件到doc中，并依据obj来设置属性，加载成功后执行回调函数fn
         * @name loadFile
         * @grammar UM.utils.loadFile(doc,obj)
         * @grammar UM.utils.loadFile(doc,obj,fn)
         * @example
         * //指定加载到当前document中一个script文件，加载成功后执行function
         * utils.loadFile( document, {
         *     src:"test.js",
         *     tag:"script",
         *     type:"text/javascript",
         *     defer:"defer"
         * }, function () {
         *     console.log('加载成功！')
         * });
         */
        loadFile: (function () {
            var tmpList = [];

            function getItem(doc, obj) {
                try {
                    for (var i = 0, ci; (ci = tmpList[i++]); ) {
                        if (ci.doc === doc && ci.url == (obj.src || obj.href)) {
                            return ci;
                        }
                    }
                } catch (e) {
                    return null;
                }
            }

            return function (doc, obj, fn) {
                var item = getItem(doc, obj);
                if (item) {
                    if (item.ready) {
                        fn && fn();
                    } else {
                        item.funs.push(fn);
                    }
                    return;
                }
                tmpList.push({
                    doc: doc,
                    url: obj.src || obj.href,
                    funs: [fn],
                });
                if (!doc.body) {
                    var html = [];
                    for (var p in obj) {
                        if (p == "tag") continue;
                        html.push(p + '="' + obj[p] + '"');
                    }
                    doc.write("<" + obj.tag + " " + html.join(" ") + " ></" + obj.tag + ">");
                    return;
                }
                if (obj.id && doc.getElementById(obj.id)) {
                    return;
                }
                var element = doc.createElement(obj.tag);
                delete obj.tag;
                for (var p in obj) {
                    element.setAttribute(p, obj[p]);
                }
                element.onload = element.onreadystatechange = function () {
                    if (!this.readyState || /loaded|complete/.test(this.readyState)) {
                        item = getItem(doc, obj);
                        if (item.funs.length > 0) {
                            item.ready = 1;
                            for (var fi; (fi = item.funs.pop()); ) {
                                fi();
                            }
                        }
                        element.onload = element.onreadystatechange = null;
                    }
                };
                element.onerror = function () {
                    throw Error("The load " + (obj.href || obj.src) + " fails,check the url settings of file umeditor.config.js ");
                };
                doc.getElementsByTagName("head")[0].appendChild(element);
            };
        })(),
        /**
         * 判断obj对象是否为空
         * @name isEmptyObject
         * @grammar UM.utils.isEmptyObject(obj)  => true|false
         * @example
         * UM.utils.isEmptyObject({}) ==>true
         * UM.utils.isEmptyObject([]) ==>true
         * UM.utils.isEmptyObject("") ==>true
         */
        isEmptyObject: function (obj) {
            if (obj == null) return true;
            if (this.isArray(obj) || this.isString(obj)) return obj.length === 0;
            for (var key in obj) if (obj.hasOwnProperty(key)) return false;
            return true;
        },

        /**
         * 统一将颜色值使用16进制形式表示
         * @name fixColor
         * @grammar UM.utils.fixColor(name,value) => value
         * @example
         * rgb(255,255,255)  => "#ffffff"
         */
        fixColor: function (name, value) {
            if (/color/i.test(name) && /rgba?/.test(value)) {
                var array = value.split(",");
                if (array.length > 3) return "";
                value = "#";
                for (var i = 0, color; (color = array[i++]); ) {
                    color = parseInt(color.replace(/[^\d]/gi, ""), 10).toString(16);
                    value += color.length == 1 ? "0" + color : color;
                }
                value = value.toUpperCase();
            }
            return value;
        },

        /**
         * 深度克隆对象，从source到target
         * @name clone
         * @grammar UM.utils.clone(source) => anthorObj 新的对象是完整的source的副本
         * @grammar UM.utils.clone(source,target) => target包含了source的所有内容，重名会覆盖
         */
        clone: function (source, target) {
            var tmp;
            target = target || {};
            for (var i in source) {
                if (source.hasOwnProperty(i)) {
                    tmp = source[i];
                    if (typeof tmp == "object") {
                        target[i] = utils.isArray(tmp) ? [] : {};
                        utils.clone(source[i], target[i]);
                    } else {
                        target[i] = tmp;
                    }
                }
            }
            return target;
        },
        /**
         * 转换cm/pt到px
         * @name transUnitToPx
         * @grammar UM.utils.transUnitToPx('20pt') => '27px'
         * @grammar UM.utils.transUnitToPx('0pt') => '0'
         */
        transUnitToPx: function (val) {
            if (!/(pt|cm)/.test(val)) {
                return val;
            }
            var unit;
            val.replace(/([\d.]+)(\w+)/, function (str, v, u) {
                val = v;
                unit = u;
            });
            switch (unit) {
                case "cm":
                    val = parseFloat(val) * 25;
                    break;
                case "pt":
                    val = Math.round((parseFloat(val) * 96) / 72);
            }
            return val + (val ? "px" : "");
        },
        /**
         * 动态添加css样式
         * @name cssRule
         * @grammar UM.utils.cssRule('添加的样式的节点名称',['样式'，'放到哪个document上'])
         * @grammar UM.utils.cssRule('body','body{background:#ccc}') => null  //给body添加背景颜色
         * @grammar UM.utils.cssRule('body') =>样式的字符串  //取得key值为body的样式的内容,如果没有找到key值先关的样式将返回空，例如刚才那个背景颜色，将返回 body{background:#ccc}
         * @grammar UM.utils.cssRule('body','') =>null //清空给定的key值的背景颜色
         */
        cssRule:
            browser.ie && browser.version != 11
                ? function (key, style, doc) {
                      var indexList, index;
                      doc = doc || document;
                      if (doc.indexList) {
                          indexList = doc.indexList;
                      } else {
                          indexList = doc.indexList = {};
                      }
                      var sheetStyle;
                      if (!indexList[key]) {
                          if (style === undefined) {
                              return "";
                          }
                          sheetStyle = doc.createStyleSheet("", (index = doc.styleSheets.length));
                          indexList[key] = index;
                      } else {
                          sheetStyle = doc.styleSheets[indexList[key]];
                      }
                      if (style === undefined) {
                          return sheetStyle.cssText;
                      }
                      sheetStyle.cssText = style || "";
                  }
                : function (key, style, doc) {
                      doc = doc || document;
                      var head = doc.getElementsByTagName("head")[0],
                          node;
                      if (!(node = doc.getElementById(key))) {
                          if (style === undefined) {
                              return "";
                          }
                          node = doc.createElement("style");
                          node.id = key;
                          head.appendChild(node);
                      }
                      if (style === undefined) {
                          return node.innerHTML;
                      }
                      if (style !== "") {
                          node.innerHTML = style;
                      } else {
                          head.removeChild(node);
                      }
                  },
    });
    /**
     * 判断str是否为字符串
     * @name isString
     * @grammar UM.utils.isString(str) => true|false
     */
    /**
     * 判断array是否为数组
     * @name isArray
     * @grammar UM.utils.isArray(obj) => true|false
     */
    /**
     * 判断obj对象是否为方法
     * @name isFunction
     * @grammar UM.utils.isFunction(obj)  => true|false
     */
    /**
     * 判断obj对象是否为数字
     * @name isNumber
     * @grammar UM.utils.isNumber(obj)  => true|false
     */
    utils.each(["String", "Function", "Array", "Number", "RegExp", "Object"], function (v) {
        UM.utils["is" + v] = function (obj) {
            return Object.prototype.toString.apply(obj) == "[object " + v + "]";
        };
    });
    /**
     * @file
     * @name UM.EventBase
     * @short EventBase
     * @import editor.js,core/utils.js
     * @desc UE采用的事件基类，继承此类的对应类将获取addListener,removeListener,fireEvent方法。
     * 在UE中，Editor以及所有ui实例都继承了该类，故可以在对应的ui对象以及editor对象上使用上述方法。
     */
    var EventBase = (UM.EventBase = function () {});

    EventBase.prototype = {
        /**
         * 注册事件监听器
         * @name addListener
         * @grammar editor.addListener(types,fn)  //types为事件名称，多个可用空格分隔
         * @example
         * editor.addListener('selectionchange',function(){
         *      console.log("选区已经变化！");
         * })
         * editor.addListener('beforegetcontent aftergetcontent',function(type){
         *         if(type == 'beforegetcontent'){
         *             //do something
         *         }else{
         *             //do something
         *         }
         *         console.log(this.getContent) // this是注册的事件的编辑器实例
         * })
         */
        addListener: function (types, listener) {
            types = utils.trim(types).split(" ");
            for (var i = 0, ti; (ti = types[i++]); ) {
                getListener(this, ti, true).push(listener);
            }
        },
        /**
         * 移除事件监听器
         * @name removeListener
         * @grammar editor.removeListener(types,fn)  //types为事件名称，多个可用空格分隔
         * @example
         * //changeCallback为方法体
         * editor.removeListener("selectionchange",changeCallback);
         */
        removeListener: function (types, listener) {
            types = utils.trim(types).split(" ");
            for (var i = 0, ti; (ti = types[i++]); ) {
                utils.removeItem(getListener(this, ti) || [], listener);
            }
        },
        /**
         * 触发事件
         * @name fireEvent
         * @grammar editor.fireEvent(types)  //types为事件名称，多个可用空格分隔
         * @example
         * editor.fireEvent("selectionchange");
         */
        fireEvent: function () {
            var types = arguments[0];
            types = utils.trim(types).split(" ");
            for (var i = 0, ti; (ti = types[i++]); ) {
                var listeners = getListener(this, ti),
                    r,
                    t,
                    k;
                if (listeners) {
                    k = listeners.length;
                    while (k--) {
                        if (!listeners[k]) continue;
                        t = listeners[k].apply(this, arguments);
                        if (t === true) {
                            return t;
                        }
                        if (t !== undefined) {
                            r = t;
                        }
                    }
                }
                if ((t = this["on" + ti.toLowerCase()])) {
                    r = t.apply(this, arguments);
                }
            }
            return r;
        },
    };
    /**
     * 获得对象所拥有监听类型的所有监听器
     * @public
     * @function
     * @param {Object} obj  查询监听器的对象
     * @param {String} type 事件类型
     * @param {Boolean} force  为true且当前所有type类型的侦听器不存在时，创建一个空监听器数组
     * @returns {Array} 监听器数组
     */
    function getListener(obj, type, force) {
        var allListeners;
        type = type.toLowerCase();
        return (allListeners = obj.__allListeners || (force && (obj.__allListeners = {}))) && (allListeners[type] || (force && (allListeners[type] = [])));
    }

    ///import editor.js
    ///import core/dom/dom.js
    ///import core/utils.js
    /**
     * dtd html语义化的体现类
     * @constructor
     * @namespace dtd
     */
    var dtd = (dom.dtd = (function () {
        function _(s) {
            for (var k in s) {
                s[k.toUpperCase()] = s[k];
            }
            return s;
        }
        var X = utils.extend2;
        var A = _({ isindex: 1, fieldset: 1 }),
            B = _({ input: 1, button: 1, select: 1, textarea: 1, label: 1 }),
            C = X(_({ a: 1 }), B),
            D = X({ iframe: 1 }, C),
            E = _({
                hr: 1,
                ul: 1,
                menu: 1,
                div: 1,
                blockquote: 1,
                noscript: 1,
                table: 1,
                center: 1,
                address: 1,
                dir: 1,
                pre: 1,
                h5: 1,
                dl: 1,
                h4: 1,
                noframes: 1,
                h6: 1,
                ol: 1,
                h1: 1,
                h3: 1,
                h2: 1,
            }),
            F = _({ ins: 1, del: 1, script: 1, style: 1 }),
            G = X(
                _({
                    b: 1,
                    acronym: 1,
                    bdo: 1,
                    var: 1,
                    "#": 1,
                    abbr: 1,
                    code: 1,
                    br: 1,
                    i: 1,
                    cite: 1,
                    kbd: 1,
                    u: 1,
                    strike: 1,
                    s: 1,
                    tt: 1,
                    strong: 1,
                    q: 1,
                    samp: 1,
                    em: 1,
                    dfn: 1,
                    span: 1,
                }),
                F
            ),
            H = X(_({ sub: 1, img: 1, embed: 1, object: 1, sup: 1, basefont: 1, map: 1, applet: 1, font: 1, big: 1, small: 1 }), G),
            I = X(_({ p: 1 }), H),
            J = X(_({ iframe: 1 }), H, B),
            K = _({
                img: 1,
                embed: 1,
                noscript: 1,
                br: 1,
                kbd: 1,
                center: 1,
                button: 1,
                basefont: 1,
                h5: 1,
                h4: 1,
                samp: 1,
                h6: 1,
                ol: 1,
                h1: 1,
                h3: 1,
                h2: 1,
                form: 1,
                font: 1,
                "#": 1,
                select: 1,
                menu: 1,
                ins: 1,
                abbr: 1,
                label: 1,
                code: 1,
                table: 1,
                script: 1,
                cite: 1,
                input: 1,
                iframe: 1,
                strong: 1,
                textarea: 1,
                noframes: 1,
                big: 1,
                small: 1,
                span: 1,
                hr: 1,
                sub: 1,
                bdo: 1,
                var: 1,
                div: 1,
                object: 1,
                sup: 1,
                strike: 1,
                dir: 1,
                map: 1,
                dl: 1,
                applet: 1,
                del: 1,
                isindex: 1,
                fieldset: 1,
                ul: 1,
                b: 1,
                acronym: 1,
                a: 1,
                blockquote: 1,
                i: 1,
                u: 1,
                s: 1,
                tt: 1,
                address: 1,
                q: 1,
                pre: 1,
                p: 1,
                em: 1,
                dfn: 1,
            }),
            L = X(_({ a: 0 }), J), //a不能被切开，所以把他
            M = _({ tr: 1 }),
            N = _({ "#": 1 }),
            O = X(_({ param: 1 }), K),
            P = X(_({ form: 1 }), A, D, E, I),
            Q = _({ li: 1, ol: 1, ul: 1 }),
            R = _({ style: 1, script: 1 }),
            S = _({ base: 1, link: 1, meta: 1, title: 1 }),
            T = X(S, R),
            U = _({ head: 1, body: 1 }),
            V = _({ html: 1 });

        var block = _({
                address: 1,
                blockquote: 1,
                center: 1,
                dir: 1,
                div: 1,
                dl: 1,
                fieldset: 1,
                form: 1,
                h1: 1,
                h2: 1,
                h3: 1,
                h4: 1,
                h5: 1,
                h6: 1,
                hr: 1,
                isindex: 1,
                menu: 1,
                noframes: 1,
                ol: 1,
                p: 1,
                pre: 1,
                table: 1,
                ul: 1,
            }),
            empty = _({
                area: 1,
                base: 1,
                basefont: 1,
                br: 1,
                col: 1,
                command: 1,
                dialog: 1,
                embed: 1,
                hr: 1,
                img: 1,
                input: 1,
                isindex: 1,
                keygen: 1,
                link: 1,
                meta: 1,
                param: 1,
                source: 1,
                track: 1,
                wbr: 1,
            });

        return _({
            // $ 表示自定的属性

            // body外的元素列表.
            $nonBodyContent: X(V, U, S),

            //块结构元素列表
            $block: block,

            //内联元素列表
            $inline: L,

            $inlineWithA: X(_({ a: 1 }), L),

            $body: X(_({ script: 1, style: 1 }), block),

            $cdata: _({ script: 1, style: 1 }),

            //自闭和元素
            $empty: empty,

            //不是自闭合，但不能让range选中里边
            $nonChild: _({ iframe: 1, textarea: 1 }),
            //列表元素列表
            $listItem: _({ dd: 1, dt: 1, li: 1 }),

            //列表根元素列表
            $list: _({ ul: 1, ol: 1, dl: 1 }),

            //不能认为是空的元素
            $isNotEmpty: _({
                table: 1,
                ul: 1,
                ol: 1,
                dl: 1,
                iframe: 1,
                area: 1,
                base: 1,
                col: 1,
                hr: 1,
                img: 1,
                embed: 1,
                input: 1,
                link: 1,
                meta: 1,
                param: 1,
                h1: 1,
                h2: 1,
                h3: 1,
                h4: 1,
                h5: 1,
                h6: 1,
            }),

            //如果没有子节点就可以删除的元素列表，像span,a
            $removeEmpty: _({
                a: 1,
                abbr: 1,
                acronym: 1,
                address: 1,
                b: 1,
                bdo: 1,
                big: 1,
                cite: 1,
                code: 1,
                del: 1,
                dfn: 1,
                em: 1,
                font: 1,
                i: 1,
                ins: 1,
                label: 1,
                kbd: 1,
                q: 1,
                s: 1,
                samp: 1,
                small: 1,
                span: 1,
                strike: 1,
                strong: 1,
                sub: 1,
                sup: 1,
                tt: 1,
                u: 1,
                var: 1,
            }),

            $removeEmptyBlock: _({ p: 1, div: 1 }),

            //在table元素里的元素列表
            $tableContent: _({ caption: 1, col: 1, colgroup: 1, tbody: 1, td: 1, tfoot: 1, th: 1, thead: 1, tr: 1, table: 1 }),
            //不转换的标签
            $notTransContent: _({ pre: 1, script: 1, style: 1, textarea: 1 }),
            html: U,
            head: T,
            style: N,
            script: N,
            body: P,
            base: {},
            link: {},
            meta: {},
            title: N,
            col: {},
            tr: _({ td: 1, th: 1 }),
            img: {},
            embed: {},
            colgroup: _({ thead: 1, col: 1, tbody: 1, tr: 1, tfoot: 1 }),
            noscript: P,
            td: P,
            br: {},
            th: P,
            center: P,
            kbd: L,
            button: X(I, E),
            basefont: {},
            h5: L,
            h4: L,
            samp: L,
            h6: L,
            ol: Q,
            h1: L,
            h3: L,
            option: N,
            h2: L,
            form: X(A, D, E, I),
            select: _({ optgroup: 1, option: 1 }),
            font: L,
            ins: L,
            menu: Q,
            abbr: L,
            label: L,
            table: _({ thead: 1, col: 1, tbody: 1, tr: 1, colgroup: 1, caption: 1, tfoot: 1 }),
            code: L,
            tfoot: M,
            cite: L,
            li: P,
            input: {},
            iframe: P,
            strong: L,
            textarea: N,
            noframes: P,
            big: L,
            small: L,
            //trace:
            span: _({ "#": 1, br: 1, b: 1, strong: 1, u: 1, i: 1, em: 1, sub: 1, sup: 1, strike: 1, span: 1 }),
            hr: L,
            dt: L,
            sub: L,
            optgroup: _({ option: 1 }),
            param: {},
            bdo: L,
            var: L,
            div: P,
            object: O,
            sup: L,
            dd: P,
            strike: L,
            area: {},
            dir: Q,
            map: X(_({ area: 1, form: 1, p: 1 }), A, F, E),
            applet: O,
            dl: _({ dt: 1, dd: 1 }),
            del: L,
            isindex: {},
            fieldset: X(_({ legend: 1 }), K),
            thead: M,
            ul: Q,
            acronym: L,
            b: L,
            a: X(_({ a: 1 }), J),
            blockquote: X(_({ td: 1, tr: 1, tbody: 1, li: 1 }), P),
            caption: L,
            i: L,
            u: L,
            tbody: M,
            s: L,
            address: X(D, I),
            tt: L,
            legend: L,
            q: L,
            pre: X(G, C),
            p: X(_({ a: 1 }), L),
            em: L,
            dfn: L,
        });
    })());

    /**
     * @file
     * @name UM.dom.domUtils
     * @short DomUtils
     * @import editor.js, core/utils.js,core/browser.js,core/dom/dtd.js
     * @desc UEditor封装的底层dom操作库
     */

    function getDomNode(node, start, ltr, startFromChild, fn, guard) {
        var tmpNode = startFromChild && node[start],
            parent;
        !tmpNode && (tmpNode = node[ltr]);
        while (!tmpNode && (parent = (parent || node).parentNode)) {
            if (parent.tagName == "BODY" || (guard && !guard(parent))) {
                return null;
            }
            tmpNode = parent[ltr];
        }
        if (tmpNode && fn && !fn(tmpNode)) {
            return getDomNode(tmpNode, start, ltr, false, fn);
        }
        return tmpNode;
    }
    var attrFix =
            ie && browser.version < 9
                ? {
                      tabindex: "tabIndex",
                      readonly: "readOnly",
                      for: "htmlFor",
                      class: "className",
                      maxlength: "maxLength",
                      cellspacing: "cellSpacing",
                      cellpadding: "cellPadding",
                      rowspan: "rowSpan",
                      colspan: "colSpan",
                      usemap: "useMap",
                      frameborder: "frameBorder",
                  }
                : {
                      tabindex: "tabIndex",
                      readonly: "readOnly",
                  },
        styleBlock = utils.listToMap([
            "-webkit-box",
            "-moz-box",
            "block",
            "list-item",
            "table",
            "table-row-group",
            "table-header-group",
            "table-footer-group",
            "table-row",
            "table-column-group",
            "table-column",
            "table-cell",
            "table-caption",
        ]);
    var domUtils = (dom.domUtils = {
        //节点常量
        NODE_ELEMENT: 1,
        NODE_DOCUMENT: 9,
        NODE_TEXT: 3,
        NODE_COMMENT: 8,
        NODE_DOCUMENT_FRAGMENT: 11,

        //位置关系
        POSITION_IDENTICAL: 0,
        POSITION_DISCONNECTED: 1,
        POSITION_FOLLOWING: 2,
        POSITION_PRECEDING: 4,
        POSITION_IS_CONTAINED: 8,
        POSITION_CONTAINS: 16,
        //ie6使用其他的会有一段空白出现
        fillChar: ie && browser.version == "6" ? "\ufeff" : "\u200B",
        //-------------------------Node部分--------------------------------
        keys: {
            /*Backspace*/ 8: 1,
            /*Delete*/ 46: 1,
            /*Shift*/ 16: 1,
            /*Ctrl*/ 17: 1,
            /*Alt*/ 18: 1,
            37: 1,
            38: 1,
            39: 1,
            40: 1,
            13: 1 /*enter*/,
        },
        breakParent: function (node, parent) {
            var tmpNode,
                parentClone = node,
                clone = node,
                leftNodes,
                rightNodes;
            do {
                parentClone = parentClone.parentNode;
                if (leftNodes) {
                    tmpNode = parentClone.cloneNode(false);
                    tmpNode.appendChild(leftNodes);
                    leftNodes = tmpNode;
                    tmpNode = parentClone.cloneNode(false);
                    tmpNode.appendChild(rightNodes);
                    rightNodes = tmpNode;
                } else {
                    leftNodes = parentClone.cloneNode(false);
                    rightNodes = leftNodes.cloneNode(false);
                }
                while ((tmpNode = clone.previousSibling)) {
                    leftNodes.insertBefore(tmpNode, leftNodes.firstChild);
                }
                while ((tmpNode = clone.nextSibling)) {
                    rightNodes.appendChild(tmpNode);
                }
                clone = parentClone;
            } while (parent !== parentClone);
            tmpNode = parent.parentNode;
            tmpNode.insertBefore(leftNodes, parent);
            tmpNode.insertBefore(rightNodes, parent);
            tmpNode.insertBefore(node, rightNodes);
            domUtils.remove(parent);
            return node;
        },
        trimWhiteTextNode: function (node) {
            function remove(dir) {
                var child;
                while ((child = node[dir]) && child.nodeType == 3 && domUtils.isWhitespace(child)) {
                    node.removeChild(child);
                }
            }
            remove("firstChild");
            remove("lastChild");
        },
        /**
         * 获取节点A相对于节点B的位置关系
         * @name getPosition
         * @grammar UM.dom.domUtils.getPosition(nodeA,nodeB)  =>  Number
         * @example
         *  switch (returnValue) {
         *      case 0: //相等，同一节点
         *      case 1: //无关，节点不相连
         *      case 2: //跟随，即节点A头部位于节点B头部的后面
         *      case 4: //前置，即节点A头部位于节点B头部的前面
         *      case 8: //被包含，即节点A被节点B包含
         *      case 10://组合类型，即节点A满足跟随节点B且被节点B包含。实际上，如果被包含，必定跟随，所以returnValue事实上不会存在8的情况。
         *      case 16://包含，即节点A包含节点B
         *      case 20://组合类型，即节点A满足前置节点A且包含节点B。同样，如果包含，必定前置，所以returnValue事实上也不会存在16的情况
         *  }
         */
        getPosition: function (nodeA, nodeB) {
            // 如果两个节点是同一个节点
            if (nodeA === nodeB) {
                // domUtils.POSITION_IDENTICAL
                return 0;
            }
            var node,
                parentsA = [nodeA],
                parentsB = [nodeB];
            node = nodeA;
            while ((node = node.parentNode)) {
                // 如果nodeB是nodeA的祖先节点
                if (node === nodeB) {
                    // domUtils.POSITION_IS_CONTAINED + domUtils.POSITION_FOLLOWING
                    return 10;
                }
                parentsA.push(node);
            }
            node = nodeB;
            while ((node = node.parentNode)) {
                // 如果nodeA是nodeB的祖先节点
                if (node === nodeA) {
                    // domUtils.POSITION_CONTAINS + domUtils.POSITION_PRECEDING
                    return 20;
                }
                parentsB.push(node);
            }
            parentsA.reverse();
            parentsB.reverse();
            if (parentsA[0] !== parentsB[0]) {
                // domUtils.POSITION_DISCONNECTED
                return 1;
            }
            var i = -1;
            while ((i++, parentsA[i] === parentsB[i])) {}
            nodeA = parentsA[i];
            nodeB = parentsB[i];
            while ((nodeA = nodeA.nextSibling)) {
                if (nodeA === nodeB) {
                    // domUtils.POSITION_PRECEDING
                    return 4;
                }
            }
            // domUtils.POSITION_FOLLOWING
            return 2;
        },

        /**
         * 返回节点node在父节点中的索引位置
         * @name getNodeIndex
         * @grammar UM.dom.domUtils.getNodeIndex(node)  => Number  //索引值从0开始
         */
        getNodeIndex: function (node, ignoreTextNode) {
            var preNode = node,
                i = 0;
            while ((preNode = preNode.previousSibling)) {
                if (ignoreTextNode && preNode.nodeType == 3) {
                    if (preNode.nodeType != preNode.nextSibling.nodeType) {
                        i++;
                    }
                    continue;
                }
                i++;
            }
            return i;
        },

        /**
         * 检测节点node是否在节点doc的树上，实质上是检测是否被doc包含
         * @name inDoc
         * @grammar UM.dom.domUtils.inDoc(node,doc)   =>  true|false
         */
        inDoc: function (node, doc) {
            return domUtils.getPosition(node, doc) == 10;
        },
        /**
         * 查找node节点的祖先节点
         * @name findParent
         * @grammar UM.dom.domUtils.findParent(node)  => Element  // 直接返回node节点的父节点
         * @grammar UM.dom.domUtils.findParent(node,filterFn)  => Element  //filterFn为过滤函数，node作为参数，返回true时才会将node作为符合要求的节点返回
         * @grammar UM.dom.domUtils.findParent(node,filterFn,includeSelf)  => Element  //includeSelf指定是否包含自身
         */
        findParent: function (node, filterFn, includeSelf) {
            if (node && !domUtils.isBody(node)) {
                node = includeSelf ? node : node.parentNode;
                while (node) {
                    if (!filterFn || filterFn(node) || domUtils.isBody(node)) {
                        return filterFn && !filterFn(node) && domUtils.isBody(node) ? null : node;
                    }
                    node = node.parentNode;
                }
            }
            return null;
        },
        /**
         * 通过tagName查找node节点的祖先节点
         * @name findParentByTagName
         * @grammar UM.dom.domUtils.findParentByTagName(node,tagNames)   =>  Element  //tagNames支持数组，区分大小写
         * @grammar UM.dom.domUtils.findParentByTagName(node,tagNames,includeSelf)   =>  Element  //includeSelf指定是否包含自身
         * @grammar UM.dom.domUtils.findParentByTagName(node,tagNames,includeSelf,excludeFn)   =>  Element  //excludeFn指定例外过滤条件，返回true时忽略该节点
         */
        findParentByTagName: function (node, tagNames, includeSelf, excludeFn) {
            tagNames = utils.listToMap(utils.isArray(tagNames) ? tagNames : [tagNames]);
            return domUtils.findParent(
                node,
                function (node) {
                    return tagNames[node.tagName] && !(excludeFn && excludeFn(node));
                },
                includeSelf
            );
        },
        /**
         * 查找节点node的祖先节点集合
         * @name findParents
         * @grammar UM.dom.domUtils.findParents(node)  => Array  //返回一个祖先节点数组集合，不包含自身
         * @grammar UM.dom.domUtils.findParents(node,includeSelf)  => Array  //返回一个祖先节点数组集合，includeSelf指定是否包含自身
         * @grammar UM.dom.domUtils.findParents(node,includeSelf,filterFn)  => Array  //返回一个祖先节点数组集合，filterFn指定过滤条件，返回true的node将被选取
         * @grammar UM.dom.domUtils.findParents(node,includeSelf,filterFn,closerFirst)  => Array  //返回一个祖先节点数组集合，closerFirst为true的话，node的直接父亲节点是数组的第0个
         */
        findParents: function (node, includeSelf, filterFn, closerFirst) {
            var parents = includeSelf && ((filterFn && filterFn(node)) || !filterFn) ? [node] : [];
            while ((node = domUtils.findParent(node, filterFn))) {
                parents.push(node);
            }
            return closerFirst ? parents : parents.reverse();
        },

        /**
         * 在节点node后面插入新节点newNode
         * @name insertAfter
         * @grammar UM.dom.domUtils.insertAfter(node,newNode)  => newNode
         */
        insertAfter: function (node, newNode) {
            return node.parentNode.insertBefore(newNode, node.nextSibling);
        },

        /**
         * 删除节点node，并根据keepChildren指定是否保留子节点
         * @name remove
         * @grammar UM.dom.domUtils.remove(node)  =>  node
         * @grammar UM.dom.domUtils.remove(node,keepChildren)  =>  node
         */
        remove: function (node, keepChildren) {
            var parent = node.parentNode,
                child;
            if (parent) {
                if (keepChildren && node.hasChildNodes()) {
                    while ((child = node.firstChild)) {
                        parent.insertBefore(child, node);
                    }
                }
                parent.removeChild(node);
            }
            return node;
        },

        /**
         * 取得node节点的下一个兄弟节点， 如果该节点其后没有兄弟节点， 则递归查找其父节点之后的第一个兄弟节点，
         * 直到找到满足条件的节点或者递归到BODY节点之后才会结束。
         * @method getNextDomNode
         * @param { Node } node 需要获取其后的兄弟节点的节点对象
         * @return { Node | NULL } 如果找满足条件的节点， 则返回该节点， 否则返回NULL
         * @example
         * ```html
         *     <body>
         *      <div id="test">
         *          <span></span>
         *      </div>
         *      <i>xxx</i>
         * </body>
         * <script>
         *
         *     //output: i节点
         *     console.log( UE.dom.domUtils.getNextDomNode( document.getElementById( "test" ) ) );
         *
         * </script>
         * ```
         * @example
         * ```html
         * <body>
         *      <div>
         *          <span></span>
         *          <i id="test">xxx</i>
         *      </div>
         *      <b>xxx</b>
         * </body>
         * <script>
         *
         *     //由于id为test的i节点之后没有兄弟节点， 则查找其父节点（div）后面的兄弟节点
         *     //output: b节点
         *     console.log( UE.dom.domUtils.getNextDomNode( document.getElementById( "test" ) ) );
         *
         * </script>
         * ```
         */

        /**
         * 取得node节点的下一个兄弟节点， 如果startFromChild的值为ture，则先获取其子节点，
         * 如果有子节点则直接返回第一个子节点；如果没有子节点或者startFromChild的值为false，
         * 则执行<a href="#UE.dom.domUtils.getNextDomNode(Node)">getNextDomNode(Node node)</a>的查找过程。
         * @method getNextDomNode
         * @param { Node } node 需要获取其后的兄弟节点的节点对象
         * @param { Boolean } startFromChild 查找过程是否从其子节点开始
         * @return { Node | NULL } 如果找满足条件的节点， 则返回该节点， 否则返回NULL
         * @see UE.dom.domUtils.getNextDomNode(Node)
         */
        getNextDomNode: function (node, startFromChild, filterFn, guard) {
            return getDomNode(node, "firstChild", "nextSibling", startFromChild, filterFn, guard);
        },
        getPreDomNode: function (node, startFromChild, filterFn, guard) {
            return getDomNode(node, "lastChild", "previousSibling", startFromChild, filterFn, guard);
        },

        /**
         * 检测节点node是否属于bookmark节点
         * @name isBookmarkNode
         * @grammar UM.dom.domUtils.isBookmarkNode(node)  => true|false
         */
        isBookmarkNode: function (node) {
            return node.nodeType == 1 && node.id && /^_baidu_bookmark_/i.test(node.id);
        },
        /**
         * 获取节点node所在的window对象
         * @name  getWindow
         * @grammar UM.dom.domUtils.getWindow(node)  => window对象
         */
        getWindow: function (node) {
            var doc = node.ownerDocument || node;
            return doc.defaultView || doc.parentWindow;
        },

        /**
         * 获取离nodeA与nodeB最近的公共的祖先节点
         * @method  getCommonAncestor
         * @param { Node } nodeA 第一个节点
         * @param { Node } nodeB 第二个节点
         * @remind 如果给定的两个节点是同一个节点， 将直接返回该节点。
         * @return { Node | NULL } 如果未找到公共节点， 返回NULL， 否则返回最近的公共祖先节点。
         * @example
         * ```javascript
         * var commonAncestor = UE.dom.domUtils.getCommonAncestor( document.body, document.body.firstChild );
         * //output: true
         * console.log( commonAncestor.tagName.toLowerCase() === 'body' );
         * ```
         */
        getCommonAncestor: function (nodeA, nodeB) {
            if (nodeA === nodeB) return nodeA;
            var parentsA = [nodeA],
                parentsB = [nodeB],
                parent = nodeA,
                i = -1;
            while ((parent = parent.parentNode)) {
                if (parent === nodeB) {
                    return parent;
                }
                parentsA.push(parent);
            }
            parent = nodeB;
            while ((parent = parent.parentNode)) {
                if (parent === nodeA) return parent;
                parentsB.push(parent);
            }
            parentsA.reverse();
            parentsB.reverse();
            while ((i++, parentsA[i] === parentsB[i])) {}
            return i == 0 ? null : parentsA[i - 1];
        },
        /**
         * 清除node节点左右连续为空的兄弟inline节点
         * @method clearEmptySibling
         * @param { Node } node 执行的节点对象， 如果该节点的左右连续的兄弟节点是空的inline节点，
         * 则这些兄弟节点将被删除
         * @grammar UE.dom.domUtils.clearEmptySibling(node,ignoreNext)  //ignoreNext指定是否忽略右边空节点
         * @grammar UE.dom.domUtils.clearEmptySibling(node,ignoreNext,ignorePre)  //ignorePre指定是否忽略左边空节点
         * @example
         * ```html
         * <body>
         *     <div></div>
         *     <span id="test"></span>
         *     <i></i>
         *     <b></b>
         *     <em>xxx</em>
         *     <span></span>
         * </body>
         * <script>
         *
         *      UE.dom.domUtils.clearEmptySibling( document.getElementById( "test" ) );
         *
         *      //output: <div></div><span id="test"></span><em>xxx</em><span></span>
         *      console.log( document.body.innerHTML );
         *
         * </script>
         * ```
         */

        /**
         * 清除node节点左右连续为空的兄弟inline节点， 如果ignoreNext的值为true，
         * 则忽略对右边兄弟节点的操作。
         * @method clearEmptySibling
         * @param { Node } node 执行的节点对象， 如果该节点的左右连续的兄弟节点是空的inline节点，
         * @param { Boolean } ignoreNext 是否忽略忽略对右边的兄弟节点的操作
         * 则这些兄弟节点将被删除
         * @see UE.dom.domUtils.clearEmptySibling(Node)
         */

        /**
         * 清除node节点左右连续为空的兄弟inline节点， 如果ignoreNext的值为true，
         * 则忽略对右边兄弟节点的操作， 如果ignorePre的值为true，则忽略对左边兄弟节点的操作。
         * @method clearEmptySibling
         * @param { Node } node 执行的节点对象， 如果该节点的左右连续的兄弟节点是空的inline节点，
         * @param { Boolean } ignoreNext 是否忽略忽略对右边的兄弟节点的操作
         * @param { Boolean } ignorePre 是否忽略忽略对左边的兄弟节点的操作
         * 则这些兄弟节点将被删除
         * @see UE.dom.domUtils.clearEmptySibling(Node)
         */
        clearEmptySibling: function (node, ignoreNext, ignorePre) {
            function clear(next, dir) {
                var tmpNode;
                while (
                    next &&
                    !domUtils.isBookmarkNode(next) &&
                    (domUtils.isEmptyInlineElement(next) ||
                        //这里不能把空格算进来会吧空格干掉，出现文字间的空格丢掉了
                        !new RegExp("[^\t\n\r" + domUtils.fillChar + "]").test(next.nodeValue))
                ) {
                    tmpNode = next[dir];
                    domUtils.remove(next);
                    next = tmpNode;
                }
            }
            !ignoreNext && clear(node.nextSibling, "nextSibling");
            !ignorePre && clear(node.previousSibling, "previousSibling");
        },

        /**
         * 将一个文本节点node拆分成两个文本节点，offset指定拆分位置
         * @name split
         * @grammar UM.dom.domUtils.split(node,offset)  =>  TextNode  //返回从切分位置开始的后一个文本节点
         */
        split: function (node, offset) {
            var doc = node.ownerDocument;
            if (browser.ie && offset == node.nodeValue.length) {
                var next = doc.createTextNode("");
                return domUtils.insertAfter(node, next);
            }
            var retval = node.splitText(offset);
            //ie8下splitText不会跟新childNodes,我们手动触发他的更新
            if (browser.ie8) {
                var tmpNode = doc.createTextNode("");
                domUtils.insertAfter(retval, tmpNode);
                domUtils.remove(tmpNode);
            }
            return retval;
        },

        /**
         * 检测节点node是否为空节点（包括空格、换行、占位符等字符）
         * @name  isWhitespace
         * @grammar  UM.dom.domUtils.isWhitespace(node)  => true|false
         */
        isWhitespace: function (node) {
            return !new RegExp("[^ \t\n\r" + domUtils.fillChar + "]").test(node.nodeValue);
        },
        /**
         * 获取元素element相对于viewport的位置坐标
         * @name getXY
         * @grammar UM.dom.domUtils.getXY(element)  => Object //返回坐标对象{x:left,y:top}
         */
        getXY: function (element) {
            var x = 0,
                y = 0;
            while (element.offsetParent) {
                y += element.offsetTop;
                x += element.offsetLeft;
                element = element.offsetParent;
            }
            return { x: x, y: y };
        },
        /**
         * 检查节点node是否是空inline节点
         * @name  isEmptyInlineElement
         * @grammar   UM.dom.domUtils.isEmptyInlineElement(node)  => 1|0
         * @example
         * <b><i></i></b> => 1
         * <b><i></i><u></u></b> => 1
         * <b></b> => 1
         * <b>xx<i></i></b> => 0
         */
        isEmptyInlineElement: function (node) {
            if (node.nodeType != 1 || !dtd.$removeEmpty[node.tagName]) {
                return 0;
            }
            node = node.firstChild;
            while (node) {
                //如果是创建的bookmark就跳过
                if (domUtils.isBookmarkNode(node)) {
                    return 0;
                }
                if ((node.nodeType == 1 && !domUtils.isEmptyInlineElement(node)) || (node.nodeType == 3 && !domUtils.isWhitespace(node))) {
                    return 0;
                }
                node = node.nextSibling;
            }
            return 1;
        },

        /**
         * 检查节点node是否为块元素
         * @name isBlockElm
         * @grammar UM.dom.domUtils.isBlockElm(node)  => true|false
         */
        isBlockElm: function (node) {
            return node.nodeType == 1 && (dtd.$block[node.tagName] || styleBlock[domUtils.getComputedStyle(node, "display")]) && !dtd.$nonChild[node.tagName];
        },

        /**
         * 原生方法getElementsByTagName的封装
         * @name getElementsByTagName
         * @grammar UM.dom.domUtils.getElementsByTagName(node,tagName)  => Array  //节点集合数组
         */
        getElementsByTagName: function (node, name, filter) {
            if (filter && utils.isString(filter)) {
                var className = filter;
                filter = function (node) {
                    var result = false;
                    $.each(
                        utils
                            .trim(className)
                            .replace(/[ ]{2,}/g, " ")
                            .split(" "),
                        function (i, v) {
                            if ($(node).hasClass(v)) {
                                result = true;
                                return false;
                            }
                        }
                    );
                    return result;
                };
            }
            name = utils
                .trim(name)
                .replace(/[ ]{2,}/g, " ")
                .split(" ");
            var arr = [];
            for (var n = 0, ni; (ni = name[n++]); ) {
                var list = node.getElementsByTagName(ni);
                for (var i = 0, ci; (ci = list[i++]); ) {
                    if (!filter || filter(ci)) arr.push(ci);
                }
            }
            return arr;
        },

        /**
         * 设置节点node及其子节点不会被选中
         * @name unSelectable
         * @grammar UM.dom.domUtils.unSelectable(node)
         */
        unSelectable:
            (ie && browser.ie9below) || browser.opera
                ? function (node) {
                      //for ie9
                      node.onselectstart = function () {
                          return false;
                      };
                      node.onclick =
                          node.onkeyup =
                          node.onkeydown =
                              function () {
                                  return false;
                              };
                      node.unselectable = "on";
                      node.setAttribute("unselectable", "on");
                      for (var i = 0, ci; (ci = node.all[i++]); ) {
                          switch (ci.tagName.toLowerCase()) {
                              case "iframe":
                              case "textarea":
                              case "input":
                              case "select":
                                  break;
                              default:
                                  ci.unselectable = "on";
                                  node.setAttribute("unselectable", "on");
                          }
                      }
                  }
                : function (node) {
                      node.style.MozUserSelect = node.style.webkitUserSelect = node.style.msUserSelect = node.style.KhtmlUserSelect = "none";
                  },
        /**
         * 删除节点node上的属性attrNames，attrNames为属性名称数组
         * @name  removeAttributes
         * @grammar UM.dom.domUtils.removeAttributes(node,attrNames)
         * @example
         * //Before remove
         * <span style="font-size:14px;" id="test" name="followMe">xxxxx</span>
         * //Remove
         * UM.dom.domUtils.removeAttributes(node,["id","name"]);
         * //After remove
         * <span style="font-size:14px;">xxxxx</span>
         */
        removeAttributes: function (node, attrNames) {
            attrNames = utils.isArray(attrNames)
                ? attrNames
                : utils
                      .trim(attrNames)
                      .replace(/[ ]{2,}/g, " ")
                      .split(" ");
            for (var i = 0, ci; (ci = attrNames[i++]); ) {
                ci = attrFix[ci] || ci;
                switch (ci) {
                    case "className":
                        node[ci] = "";
                        break;
                    case "style":
                        node.style.cssText = "";
                        !browser.ie && node.removeAttributeNode(node.getAttributeNode("style"));
                }
                node.removeAttribute(ci);
            }
        },
        /**
         * 在doc下创建一个标签名为tag，属性为attrs的元素
         * @name createElement
         * @grammar UM.dom.domUtils.createElement(doc,tag,attrs)  =>  Node  //返回创建的节点
         */
        createElement: function (doc, tag, attrs) {
            return domUtils.setAttributes(doc.createElement(tag), attrs);
        },
        /**
         * 为节点node添加属性attrs，attrs为属性键值对
         * @name setAttributes
         * @grammar UM.dom.domUtils.setAttributes(node,attrs)  => node
         */
        setAttributes: function (node, attrs) {
            for (var attr in attrs) {
                if (attrs.hasOwnProperty(attr)) {
                    var value = attrs[attr];
                    switch (attr) {
                        case "class":
                            //ie下要这样赋值，setAttribute不起作用
                            node.className = value;
                            break;
                        case "style":
                            node.style.cssText = node.style.cssText + ";" + value;
                            break;
                        case "innerHTML":
                            node[attr] = value;
                            break;
                        case "value":
                            node.value = value;
                            break;
                        default:
                            node.setAttribute(attrFix[attr] || attr, value);
                    }
                }
            }
            return node;
        },

        /**
         * 获取元素element的计算样式
         * @name getComputedStyle
         * @grammar UM.dom.domUtils.getComputedStyle(element,styleName)  => String //返回对应样式名称的样式值
         * @example
         * getComputedStyle(document.body,"font-size")  =>  "15px"
         * getComputedStyle(form,"color")  =>  "#ffccdd"
         */
        getComputedStyle: function (element, styleName) {
            return utils.transUnitToPx(utils.fixColor(styleName, $(element).css(styleName)));
        },

        /**
         * 阻止事件默认行为
         * @param {Event} evt    需要组织的事件对象
         */
        preventDefault: function (evt) {
            evt.preventDefault ? evt.preventDefault() : (evt.returnValue = false);
        },

        /**
         * 删除元素element指定的样式
         * @method removeStyle
         * @param { Element } element 需要删除样式的元素
         * @param { String } styleName 需要删除的样式名
         * @example
         * ```html
         * <span id="test" style="color: red; background: blue;"></span>
         *
         * <script>
         *
         *     var testNode = document.getElementById("test");
         *
         *     UE.dom.domUtils.removeStyle( testNode, 'color' );
         *
         *     //output: background: blue;
         *     console.log( testNode.style.cssText );
         *
         * </script>
         * ```
         */
        removeStyle: function (element, name) {
            if (browser.ie) {
                //针对color先单独处理一下
                if (name == "color") {
                    name = "(^|;)" + name;
                }
                element.style.cssText = element.style.cssText.replace(new RegExp(name + "[^:]*:[^;]+;?", "ig"), "");
            } else {
                if (element.style.removeProperty) {
                    element.style.removeProperty(name);
                } else {
                    element.style.removeAttribute(utils.cssStyleToDomStyle(name));
                }
            }

            if (!element.style.cssText) {
                domUtils.removeAttributes(element, ["style"]);
            }
        },

        /**
         * 获取元素element的某个样式值
         * @name getStyle
         * @grammar UM.dom.domUtils.getStyle(element,name)  => String
         */
        getStyle: function (element, name) {
            var value = element.style[utils.cssStyleToDomStyle(name)];
            return utils.fixColor(name, value);
        },
        /**
         * 为元素element设置样式属性值
         * @name setStyle
         * @grammar UM.dom.domUtils.setStyle(element,name,value)
         */
        setStyle: function (element, name, value) {
            element.style[utils.cssStyleToDomStyle(name)] = value;
            if (!utils.trim(element.style.cssText)) {
                this.removeAttributes(element, "style");
            }
        },

        /**
         * 删除_moz_dirty属性
         * @function
         */
        removeDirtyAttr: function (node) {
            for (var i = 0, ci, nodes = node.getElementsByTagName("*"); (ci = nodes[i++]); ) {
                ci.removeAttribute("_moz_dirty");
            }
            node.removeAttribute("_moz_dirty");
        },
        /**
         * 返回子节点的数量
         * @function
         * @param {Node}    node    父节点
         * @param  {Function}    fn    过滤子节点的规则，若为空，则得到所有子节点的数量
         * @return {Number}    符合条件子节点的数量
         */
        getChildCount: function (node, fn) {
            var count = 0,
                first = node.firstChild;
            fn =
                fn ||
                function () {
                    return 1;
                };
            while (first) {
                if (fn(first)) {
                    count++;
                }
                first = first.nextSibling;
            }
            return count;
        },

        /**
         * 判断是否为空节点
         * @function
         * @param {Node}    node    节点
         * @return {Boolean}    是否为空节点
         */
        isEmptyNode: function (node) {
            return (
                !node.firstChild ||
                domUtils.getChildCount(node, function (node) {
                    return !domUtils.isBr(node) && !domUtils.isBookmarkNode(node) && !domUtils.isWhitespace(node);
                }) == 0
            );
        },

        /**
         * 判断节点是否为br
         * @function
         * @param {Node}    node   节点
         */
        isBr: function (node) {
            return node.nodeType == 1 && node.tagName == "BR";
        },
        isFillChar: function (node, isInStart) {
            return node.nodeType == 3 && !node.nodeValue.replace(new RegExp((isInStart ? "^" : "") + domUtils.fillChar), "").length;
        },

        isEmptyBlock: function (node, reg) {
            if (node.nodeType != 1) return 0;
            reg = reg || new RegExp("[ \t\r\n" + domUtils.fillChar + "]", "g");
            if (node[browser.ie ? "innerText" : "textContent"].replace(reg, "").length > 0) {
                return 0;
            }
            for (var n in dtd.$isNotEmpty) {
                if (node.getElementsByTagName(n).length) {
                    return 0;
                }
            }
            return 1;
        },

        //判断是否是编辑器自定义的参数
        isCustomeNode: function (node) {
            return node.nodeType == 1 && node.getAttribute("_ue_custom_node_");
        },
        fillNode: function (doc, node) {
            var tmpNode = browser.ie ? doc.createTextNode(domUtils.fillChar) : doc.createElement("br");
            node.innerHTML = "";
            node.appendChild(tmpNode);
        },
        isBoundaryNode: function (node, dir) {
            var tmp;
            while (!domUtils.isBody(node)) {
                tmp = node;
                node = node.parentNode;
                if (tmp !== node[dir]) {
                    return false;
                }
            }
            return true;
        },
        isFillChar: function (node, isInStart) {
            return node.nodeType == 3 && !node.nodeValue.replace(new RegExp((isInStart ? "^" : "") + domUtils.fillChar), "").length;
        },
        isBody: function (node) {
            return $(node).hasClass("edui-body-container");
        },
    });
    var fillCharReg = new RegExp(domUtils.fillChar, "g");
    ///import editor.js
    ///import core/utils.js
    ///import core/browser.js
    ///import core/dom/dom.js
    ///import core/dom/dtd.js
    ///import core/dom/domUtils.js
    /**
     * @file
     * @name UM.dom.Range
     * @anthor zhanyi
     * @short Range
     * @import editor.js,core/utils.js,core/browser.js,core/dom/domUtils.js,core/dom/dtd.js
     * @desc Range范围实现类，本类是UEditor底层核心类，统一w3cRange和ieRange之间的差异，包括接口和属性
     */
    (function () {
        var guid = 0,
            fillChar = domUtils.fillChar,
            fillData;

        /**
         * 更新range的collapse状态
         * @param  {Range}   range    range对象
         */
        function updateCollapse(range) {
            range.collapsed = range.startContainer && range.endContainer && range.startContainer === range.endContainer && range.startOffset == range.endOffset;
        }

        function selectOneNode(rng) {
            return !rng.collapsed && rng.startContainer.nodeType == 1 && rng.startContainer === rng.endContainer && rng.endOffset - rng.startOffset == 1;
        }
        function setEndPoint(toStart, node, offset, range) {
            //如果node是自闭合标签要处理
            if (node.nodeType == 1 && (dtd.$empty[node.tagName] || dtd.$nonChild[node.tagName])) {
                offset = domUtils.getNodeIndex(node) + (toStart ? 0 : 1);
                node = node.parentNode;
            }
            if (toStart) {
                range.startContainer = node;
                range.startOffset = offset;
                if (!range.endContainer) {
                    range.collapse(true);
                }
            } else {
                range.endContainer = node;
                range.endOffset = offset;
                if (!range.startContainer) {
                    range.collapse(false);
                }
            }
            updateCollapse(range);
            return range;
        }

        /**
         * @name Range
         * @grammar new UM.dom.Range(document)  => Range 实例
         * @desc 创建一个跟document绑定的空的Range实例
         * - ***startContainer*** 开始边界的容器节点,可以是elementNode或者是textNode
         * - ***startOffset*** 容器节点中的偏移量，如果是elementNode就是childNodes中的第几个，如果是textNode就是nodeValue的第几个字符
         * - ***endContainer*** 结束边界的容器节点,可以是elementNode或者是textNode
         * - ***endOffset*** 容器节点中的偏移量，如果是elementNode就是childNodes中的第几个，如果是textNode就是nodeValue的第几个字符
         * - ***document*** 跟range关联的document对象
         * - ***collapsed*** 是否是闭合状态
         */
        var Range = (dom.Range = function (document, body) {
            var me = this;
            me.startContainer = me.startOffset = me.endContainer = me.endOffset = null;
            me.document = document;
            me.collapsed = true;
            me.body = body;
        });

        /**
         * 删除fillData
         * @param doc
         * @param excludeNode
         */
        function removeFillData(doc, excludeNode) {
            try {
                if (fillData && domUtils.inDoc(fillData, doc)) {
                    if (!fillData.nodeValue.replace(fillCharReg, "").length) {
                        var tmpNode = fillData.parentNode;
                        domUtils.remove(fillData);
                        while (
                            tmpNode &&
                            domUtils.isEmptyInlineElement(tmpNode) &&
                            //safari的contains有bug
                            (browser.safari ? !(domUtils.getPosition(tmpNode, excludeNode) & domUtils.POSITION_CONTAINS) : !tmpNode.contains(excludeNode))
                        ) {
                            fillData = tmpNode.parentNode;
                            domUtils.remove(tmpNode);
                            tmpNode = fillData;
                        }
                    } else {
                        fillData.nodeValue = fillData.nodeValue.replace(fillCharReg, "");
                    }
                }
            } catch (e) {}
        }

        /**
         *
         * @param node
         * @param dir
         */
        function mergeSibling(node, dir) {
            var tmpNode;
            node = node[dir];
            while (node && domUtils.isFillChar(node)) {
                tmpNode = node[dir];
                domUtils.remove(node);
                node = tmpNode;
            }
        }

        function execContentsAction(range, action) {
            //调整边界
            //range.includeBookmark();
            var start = range.startContainer,
                end = range.endContainer,
                startOffset = range.startOffset,
                endOffset = range.endOffset,
                doc = range.document,
                frag = doc.createDocumentFragment(),
                tmpStart,
                tmpEnd;
            if (start.nodeType == 1) {
                start = start.childNodes[startOffset] || (tmpStart = start.appendChild(doc.createTextNode("")));
            }
            if (end.nodeType == 1) {
                end = end.childNodes[endOffset] || (tmpEnd = end.appendChild(doc.createTextNode("")));
            }
            if (start === end && start.nodeType == 3) {
                frag.appendChild(doc.createTextNode(start.substringData(startOffset, endOffset - startOffset)));
                //is not clone
                if (action) {
                    start.deleteData(startOffset, endOffset - startOffset);
                    range.collapse(true);
                }
                return frag;
            }
            var current,
                currentLevel,
                clone = frag,
                startParents = domUtils.findParents(start, true),
                endParents = domUtils.findParents(end, true);
            for (var i = 0; startParents[i] == endParents[i]; ) {
                i++;
            }
            for (var j = i, si; (si = startParents[j]); j++) {
                current = si.nextSibling;
                if (si == start) {
                    if (!tmpStart) {
                        if (range.startContainer.nodeType == 3) {
                            clone.appendChild(doc.createTextNode(start.nodeValue.slice(startOffset)));
                            //is not clone
                            if (action) {
                                start.deleteData(startOffset, start.nodeValue.length - startOffset);
                            }
                        } else {
                            clone.appendChild(!action ? start.cloneNode(true) : start);
                        }
                    }
                } else {
                    currentLevel = si.cloneNode(false);
                    clone.appendChild(currentLevel);
                }
                while (current) {
                    if (current === end || current === endParents[j]) {
                        break;
                    }
                    si = current.nextSibling;
                    clone.appendChild(!action ? current.cloneNode(true) : current);
                    current = si;
                }
                clone = currentLevel;
            }
            clone = frag;
            if (!startParents[i]) {
                clone.appendChild(startParents[i - 1].cloneNode(false));
                clone = clone.firstChild;
            }
            for (var j = i, ei; (ei = endParents[j]); j++) {
                current = ei.previousSibling;
                if (ei == end) {
                    if (!tmpEnd && range.endContainer.nodeType == 3) {
                        clone.appendChild(doc.createTextNode(end.substringData(0, endOffset)));
                        //is not clone
                        if (action) {
                            end.deleteData(0, endOffset);
                        }
                    }
                } else {
                    currentLevel = ei.cloneNode(false);
                    clone.appendChild(currentLevel);
                }
                //如果两端同级，右边第一次已经被开始做了
                if (j != i || !startParents[i]) {
                    while (current) {
                        if (current === start) {
                            break;
                        }
                        ei = current.previousSibling;
                        clone.insertBefore(!action ? current.cloneNode(true) : current, clone.firstChild);
                        current = ei;
                    }
                }
                clone = currentLevel;
            }
            if (action) {
                range.setStartBefore(!endParents[i] ? endParents[i - 1] : !startParents[i] ? startParents[i - 1] : endParents[i]).collapse(true);
            }
            tmpStart && domUtils.remove(tmpStart);
            tmpEnd && domUtils.remove(tmpEnd);
            return frag;
        }
        Range.prototype = {
            /**
             * @name deleteContents
             * @grammar range.deleteContents()  => Range
             * @desc 删除当前选区范围中的所有内容并返回range实例，这时的range已经变成了闭合状态
             * @example
             * DOM Element :
             * <b>x<i>x[x<i>xx]x</b>
             * //执行方法后
             * <b>x<i>x<i>|x</b>
             * 注意range改变了
             * range.startContainer => b
             * range.startOffset  => 2
             * range.endContainer => b
             * range.endOffset => 2
             * range.collapsed => true
             */
            deleteContents: function () {
                var txt;
                if (!this.collapsed) {
                    execContentsAction(this, 1);
                }
                if (browser.webkit) {
                    txt = this.startContainer;
                    if (txt.nodeType == 3 && !txt.nodeValue.length) {
                        this.setStartBefore(txt).collapse(true);
                        domUtils.remove(txt);
                    }
                }
                return this;
            },
            inFillChar: function () {
                var start = this.startContainer;
                if (this.collapsed && start.nodeType == 3 && start.nodeValue.replace(new RegExp("^" + domUtils.fillChar), "").length + 1 == start.nodeValue.length) {
                    return true;
                }
                return false;
            },
            /**
             * @name  setStart
             * @grammar range.setStart(node,offset)  => Range
             * @desc    设置range的开始位置位于node节点内，偏移量为offset
             * 如果node是elementNode那offset指的是childNodes中的第几个，如果是textNode那offset指的是nodeValue的第几个字符
             */
            setStart: function (node, offset) {
                return setEndPoint(true, node, offset, this);
            },
            /**
             * 设置range的结束位置位于node节点，偏移量为offset
             * 如果node是elementNode那offset指的是childNodes中的第几个，如果是textNode那offset指的是nodeValue的第几个字符
             * @name  setEnd
             * @grammar range.setEnd(node,offset)  => Range
             */
            setEnd: function (node, offset) {
                return setEndPoint(false, node, offset, this);
            },
            /**
             * 将Range开始位置设置到node节点之后
             * @name  setStartAfter
             * @grammar range.setStartAfter(node)  => Range
             * @example
             * <b>xx<i>x|x</i>x</b>
             * 执行setStartAfter(i)后
             * range.startContainer =>b
             * range.startOffset =>2
             */
            setStartAfter: function (node) {
                return this.setStart(node.parentNode, domUtils.getNodeIndex(node) + 1);
            },
            /**
             * 将Range开始位置设置到node节点之前
             * @name  setStartBefore
             * @grammar range.setStartBefore(node)  => Range
             * @example
             * <b>xx<i>x|x</i>x</b>
             * 执行setStartBefore(i)后
             * range.startContainer =>b
             * range.startOffset =>1
             */
            setStartBefore: function (node) {
                return this.setStart(node.parentNode, domUtils.getNodeIndex(node));
            },
            /**
             * 将Range结束位置设置到node节点之后
             * @name  setEndAfter
             * @grammar range.setEndAfter(node)  => Range
             * @example
             * <b>xx<i>x|x</i>x</b>
             * setEndAfter(i)后
             * range.endContainer =>b
             * range.endtOffset =>2
             */
            setEndAfter: function (node) {
                return this.setEnd(node.parentNode, domUtils.getNodeIndex(node) + 1);
            },
            /**
             * 将Range结束位置设置到node节点之前
             * @name  setEndBefore
             * @grammar range.setEndBefore(node)  => Range
             * @example
             * <b>xx<i>x|x</i>x</b>
             * 执行setEndBefore(i)后
             * range.endContainer =>b
             * range.endtOffset =>1
             */
            setEndBefore: function (node) {
                return this.setEnd(node.parentNode, domUtils.getNodeIndex(node));
            },
            /**
             * 将Range开始位置设置到node节点内的开始位置
             * @name  setStartAtFirst
             * @grammar range.setStartAtFirst(node)  => Range
             */
            setStartAtFirst: function (node) {
                return this.setStart(node, 0);
            },
            /**
             * 将Range开始位置设置到node节点内的结束位置
             * @name  setStartAtLast
             * @grammar range.setStartAtLast(node)  => Range
             */
            setStartAtLast: function (node) {
                return this.setStart(node, node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length);
            },
            /**
             * 将Range结束位置设置到node节点内的开始位置
             * @name  setEndAtFirst
             * @grammar range.setEndAtFirst(node)  => Range
             */
            setEndAtFirst: function (node) {
                return this.setEnd(node, 0);
            },
            /**
             * 将Range结束位置设置到node节点内的结束位置
             * @name  setEndAtLast
             * @grammar range.setEndAtLast(node)  => Range
             */
            setEndAtLast: function (node) {
                return this.setEnd(node, node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length);
            },

            /**
             * 选中完整的指定节点,并返回包含该节点的range
             * @name  selectNode
             * @grammar range.selectNode(node)  => Range
             */
            selectNode: function (node) {
                return this.setStartBefore(node).setEndAfter(node);
            },
            /**
             * 选中node内部的所有节点，并返回对应的range
             * @name selectNodeContents
             * @grammar range.selectNodeContents(node)  => Range
             * @example
             * <b>xx[x<i>xxx</i>]xxx</b>
             * 执行后
             * <b>[xxx<i>xxx</i>xxx]</b>
             * range.startContainer =>b
             * range.startOffset =>0
             * range.endContainer =>b
             * range.endOffset =>3
             */
            selectNodeContents: function (node) {
                return this.setStart(node, 0).setEndAtLast(node);
            },

            /**
             * 克隆一个新的range对象
             * @name  cloneRange
             * @grammar range.cloneRange() => Range
             */
            cloneRange: function () {
                var me = this;
                return new Range(me.document).setStart(me.startContainer, me.startOffset).setEnd(me.endContainer, me.endOffset);
            },

            /**
             * 让选区闭合到尾部，若toStart为真，则闭合到头部
             * @name  collapse
             * @grammar range.collapse() => Range
             * @grammar range.collapse(true) => Range   //闭合选区到头部
             */
            collapse: function (toStart) {
                var me = this;
                if (toStart) {
                    me.endContainer = me.startContainer;
                    me.endOffset = me.startOffset;
                } else {
                    me.startContainer = me.endContainer;
                    me.startOffset = me.endOffset;
                }
                me.collapsed = true;
                return me;
            },

            /**
             * 调整range的边界，使其"收缩"到最小的位置
             * @name  shrinkBoundary
             * @grammar range.shrinkBoundary()  => Range  //range开始位置和结束位置都调整，参见<code><a href="#adjustmentboundary">adjustmentBoundary</a></code>
             * @grammar range.shrinkBoundary(true)  => Range  //仅调整开始位置，忽略结束位置
             * @example
             * <b>xx[</b>xxxxx] ==> <b>xx</b>[xxxxx]
             * <b>x[xx</b><i>]xxx</i> ==> <b>x[xx]</b><i>xxx</i>
             * [<b><i>xxxx</i>xxxxxxx</b>] ==> <b><i>[xxxx</i>xxxxxxx]</b>
             */
            shrinkBoundary: function (ignoreEnd) {
                var me = this,
                    child,
                    collapsed = me.collapsed;
                function check(node) {
                    return node.nodeType == 1 && !domUtils.isBookmarkNode(node) && !dtd.$empty[node.tagName] && !dtd.$nonChild[node.tagName];
                }
                while (
                    me.startContainer.nodeType == 1 && //是element
                    (child = me.startContainer.childNodes[me.startOffset]) && //子节点也是element
                    check(child)
                ) {
                    me.setStart(child, 0);
                }
                if (collapsed) {
                    return me.collapse(true);
                }
                if (!ignoreEnd) {
                    while (
                        me.endContainer.nodeType == 1 && //是element
                        me.endOffset > 0 && //如果是空元素就退出 endOffset=0那么endOffst-1为负值，childNodes[endOffset]报错
                        (child = me.endContainer.childNodes[me.endOffset - 1]) && //子节点也是element
                        check(child)
                    ) {
                        me.setEnd(child, child.childNodes.length);
                    }
                }
                return me;
            },

            /**
             * 调整边界容器，如果是textNode,就调整到elementNode上
             * @name trimBoundary
             * @grammar range.trimBoundary([ignoreEnd])  => Range //true忽略结束边界
             * @example
             * DOM Element :
             * <b>|xxx</b>
             * startContainer = xxx; startOffset = 0
             * //执行后本方法后
             * startContainer = <b>;  startOffset = 0
             * @example
             * Dom Element :
             * <b>xx|x</b>
             * startContainer = xxx;  startOffset = 2
             * //执行本方法后，xxx被实实在在地切分成两个TextNode
             * startContainer = <b>; startOffset = 1
             */
            trimBoundary: function (ignoreEnd) {
                this.txtToElmBoundary();
                var start = this.startContainer,
                    offset = this.startOffset,
                    collapsed = this.collapsed,
                    end = this.endContainer;
                if (start.nodeType == 3) {
                    if (offset == 0) {
                        this.setStartBefore(start);
                    } else {
                        if (offset >= start.nodeValue.length) {
                            this.setStartAfter(start);
                        } else {
                            var textNode = domUtils.split(start, offset);
                            //跟新结束边界
                            if (start === end) {
                                this.setEnd(textNode, this.endOffset - offset);
                            } else if (start.parentNode === end) {
                                this.endOffset += 1;
                            }
                            this.setStartBefore(textNode);
                        }
                    }
                    if (collapsed) {
                        return this.collapse(true);
                    }
                }
                if (!ignoreEnd) {
                    offset = this.endOffset;
                    end = this.endContainer;
                    if (end.nodeType == 3) {
                        if (offset == 0) {
                            this.setEndBefore(end);
                        } else {
                            offset < end.nodeValue.length && domUtils.split(end, offset);
                            this.setEndAfter(end);
                        }
                    }
                }
                return this;
            },
            /**
             * 如果选区在文本的边界上，就扩展选区到文本的父节点上
             * @name  txtToElmBoundary
             * @example
             * Dom Element :
             * <b> |xxx</b>
             * startContainer = xxx;  startOffset = 0
             * //本方法执行后
             * startContainer = <b>; startOffset = 0
             * @example
             * Dom Element :
             * <b> xxx| </b>
             * startContainer = xxx; startOffset = 3
             * //本方法执行后
             * startContainer = <b>; startOffset = 1
             */
            txtToElmBoundary: function (ignoreCollapsed) {
                function adjust(r, c) {
                    var container = r[c + "Container"],
                        offset = r[c + "Offset"];
                    if (container.nodeType == 3) {
                        if (!offset) {
                            r[
                                "set" +
                                    c.replace(/(\w)/, function (a) {
                                        return a.toUpperCase();
                                    }) +
                                    "Before"
                            ](container);
                        } else if (offset >= container.nodeValue.length) {
                            r[
                                "set" +
                                    c.replace(/(\w)/, function (a) {
                                        return a.toUpperCase();
                                    }) +
                                    "After"
                            ](container);
                        }
                    }
                }

                if (ignoreCollapsed || !this.collapsed) {
                    adjust(this, "start");
                    adjust(this, "end");
                }
                return this;
            },

            /**
             * 在当前选区的开始位置前插入一个节点或者fragment，range的开始位置会在插入节点的前边
             * @name  insertNode
             * @grammar range.insertNode(node)  => Range //node可以是textNode,elementNode,fragment
             * @example
             * Range :
             * xxx[x<p>xxxx</p>xxxx]x<p>sdfsdf</p>
             * 待插入Node :
             * <p>ssss</p>
             * 执行本方法后的Range :
             * xxx[<p>ssss</p>x<p>xxxx</p>xxxx]x<p>sdfsdf</p>
             */
            insertNode: function (node) {
                var first = node,
                    length = 1;
                if (node.nodeType == 11) {
                    first = node.firstChild;
                    length = node.childNodes.length;
                }
                this.trimBoundary(true);
                var start = this.startContainer,
                    offset = this.startOffset;
                var nextNode = start.childNodes[offset];
                if (nextNode) {
                    start.insertBefore(node, nextNode);
                } else {
                    start.appendChild(node);
                }
                if (first.parentNode === this.endContainer) {
                    this.endOffset = this.endOffset + length;
                }
                return this.setStartBefore(first);
            },
            /**
             * 设置光标闭合位置,toEnd设置为true时光标将闭合到选区的结尾
             * @name  setCursor
             * @grammar range.setCursor([toEnd])  =>  Range   //toEnd为true时，光标闭合到选区的末尾
             */
            setCursor: function (toEnd, noFillData) {
                return this.collapse(!toEnd).select(noFillData);
            },
            /**
             * 创建当前range的一个书签，记录下当前range的位置，方便当dom树改变时，还能找回原来的选区位置
             * @name createBookmark
             * @grammar range.createBookmark([serialize])  => Object  //{start:开始标记,end:结束标记,id:serialize} serialize为真时，开始结束标记是插入节点的id，否则是插入节点的引用
             */
            createBookmark: function (serialize, same) {
                var endNode,
                    startNode = this.document.createElement("span");
                startNode.style.cssText = "display:none;line-height:0px;";
                startNode.appendChild(this.document.createTextNode("\u200D"));
                startNode.id = "_baidu_bookmark_start_" + (same ? "" : guid++);

                if (!this.collapsed) {
                    endNode = startNode.cloneNode(true);
                    endNode.id = "_baidu_bookmark_end_" + (same ? "" : guid++);
                }
                this.insertNode(startNode);
                if (endNode) {
                    this.collapse().insertNode(endNode).setEndBefore(endNode);
                }
                this.setStartAfter(startNode);
                return {
                    start: serialize ? startNode.id : startNode,
                    end: endNode ? (serialize ? endNode.id : endNode) : null,
                    id: serialize,
                };
            },
            /**
             *  移动边界到书签位置，并删除插入的书签节点
             *  @name  moveToBookmark
             *  @grammar range.moveToBookmark(bookmark)  => Range //让当前的range选到给定bookmark的位置,bookmark对象是由range.createBookmark创建的
             */
            moveToBookmark: function (bookmark) {
                var start = bookmark.id ? this.document.getElementById(bookmark.start) : bookmark.start,
                    end = bookmark.end && bookmark.id ? this.document.getElementById(bookmark.end) : bookmark.end;
                this.setStartBefore(start);
                domUtils.remove(start);
                if (end) {
                    this.setEndBefore(end);
                    domUtils.remove(end);
                } else {
                    this.collapse(true);
                }
                return this;
            },

            /**
             * 调整Range的边界，使其"缩小"到最合适的位置
             * @name adjustmentBoundary
             * @grammar range.adjustmentBoundary() => Range   //参见<code><a href="#shrinkboundary">shrinkBoundary</a></code>
             * @example
             * <b>xx[</b>xxxxx] ==> <b>xx</b>[xxxxx]
             * <b>x[xx</b><i>]xxx</i> ==> <b>x[xx</b>]<i>xxx</i>
             */
            adjustmentBoundary: function () {
                if (!this.collapsed) {
                    while (
                        !domUtils.isBody(this.startContainer) &&
                        this.startOffset == this.startContainer[this.startContainer.nodeType == 3 ? "nodeValue" : "childNodes"].length &&
                        this.startContainer[this.startContainer.nodeType == 3 ? "nodeValue" : "childNodes"].length
                    ) {
                        this.setStartAfter(this.startContainer);
                    }
                    while (!domUtils.isBody(this.endContainer) && !this.endOffset && this.endContainer[this.endContainer.nodeType == 3 ? "nodeValue" : "childNodes"].length) {
                        this.setEndBefore(this.endContainer);
                    }
                }
                return this;
            },

            /**
             * 得到一个自闭合的节点,常用于获取自闭和的节点，例如图片节点
             * @name  getClosedNode
             * @grammar range.getClosedNode()  => node|null
             * @example
             * <b>xxxx[<img />]xxx</b>
             */
            getClosedNode: function () {
                var node;
                if (!this.collapsed) {
                    var range = this.cloneRange().adjustmentBoundary().shrinkBoundary();
                    if (selectOneNode(range)) {
                        var child = range.startContainer.childNodes[range.startOffset];
                        if (child && child.nodeType == 1 && (dtd.$empty[child.tagName] || dtd.$nonChild[child.tagName])) {
                            node = child;
                        }
                    }
                }
                return node;
            },
            /**
             * 根据当前range选中内容节点（在页面上表现为反白显示）
             * @name select
             * @grammar range.select();  => Range
             */
            select: browser.ie
                ? function (noFillData, textRange) {
                      var nativeRange;
                      if (!this.collapsed) this.shrinkBoundary();
                      var node = this.getClosedNode();
                      if (node && !textRange) {
                          try {
                              nativeRange = this.document.body.createControlRange();
                              nativeRange.addElement(node);
                              nativeRange.select();
                          } catch (e) {}
                          return this;
                      }
                      var bookmark = this.createBookmark(),
                          start = bookmark.start,
                          end;
                      nativeRange = this.document.body.createTextRange();
                      nativeRange.moveToElementText(start);
                      nativeRange.moveStart("character", 1);
                      if (!this.collapsed) {
                          var nativeRangeEnd = this.document.body.createTextRange();
                          end = bookmark.end;
                          nativeRangeEnd.moveToElementText(end);
                          nativeRange.setEndPoint("EndToEnd", nativeRangeEnd);
                      } else {
                          if (!noFillData && this.startContainer.nodeType != 3) {
                              //使用<span>|x<span>固定住光标
                              var tmpText = this.document.createTextNode(fillChar),
                                  tmp = this.document.createElement("span");
                              tmp.appendChild(this.document.createTextNode(fillChar));
                              start.parentNode.insertBefore(tmp, start);
                              start.parentNode.insertBefore(tmpText, start);
                              //当点b,i,u时，不能清除i上边的b
                              removeFillData(this.document, tmpText);
                              fillData = tmpText;
                              mergeSibling(tmp, "previousSibling");
                              mergeSibling(start, "nextSibling");
                              nativeRange.moveStart("character", -1);
                              nativeRange.collapse(true);
                          }
                      }
                      this.moveToBookmark(bookmark);
                      tmp && domUtils.remove(tmp);
                      //IE在隐藏状态下不支持range操作，catch一下
                      try {
                          nativeRange.select();
                      } catch (e) {}
                      return this;
                  }
                : function (notInsertFillData) {
                      function checkOffset(rng) {
                          function check(node, offset, dir) {
                              if (node.nodeType == 3 && node.nodeValue.length < offset) {
                                  rng[dir + "Offset"] = node.nodeValue.length;
                              }
                          }
                          check(rng.startContainer, rng.startOffset, "start");
                          check(rng.endContainer, rng.endOffset, "end");
                      }
                      var win = domUtils.getWindow(this.document),
                          sel = win.getSelection(),
                          txtNode;
                      //FF下关闭自动长高时滚动条在关闭dialog时会跳
                      //ff下如果不body.focus将不能定位闭合光标到编辑器内
                      browser.gecko ? this.body.focus() : win.focus();
                      if (sel) {
                          sel.removeAllRanges();
                          // trace:870 chrome/safari后边是br对于闭合得range不能定位 所以去掉了判断
                          // this.startContainer.nodeType != 3 &&! ((child = this.startContainer.childNodes[this.startOffset]) && child.nodeType == 1 && child.tagName == 'BR'
                          if (this.collapsed && !notInsertFillData) {
                              //                    //opear如果没有节点接着，原生的不能够定位,不能在body的第一级插入空白节点
                              //                    if (notInsertFillData && browser.opera && !domUtils.isBody(this.startContainer) && this.startContainer.nodeType == 1) {
                              //                        var tmp = this.document.createTextNode('');
                              //                        this.insertNode(tmp).setStart(tmp, 0).collapse(true);
                              //                    }
                              //
                              //处理光标落在文本节点的情况
                              //处理以下的情况
                              //<b>|xxxx</b>
                              //<b>xxxx</b>|xxxx
                              //xxxx<b>|</b>
                              var start = this.startContainer,
                                  child = start;
                              if (start.nodeType == 1) {
                                  child = start.childNodes[this.startOffset];
                              }
                              if (
                                  !(start.nodeType == 3 && this.startOffset) &&
                                  (child ? !child.previousSibling || child.previousSibling.nodeType != 3 : !start.lastChild || start.lastChild.nodeType != 3)
                              ) {
                                  txtNode = this.document.createTextNode(fillChar);
                                  //跟着前边走
                                  this.insertNode(txtNode);
                                  removeFillData(this.document, txtNode);
                                  mergeSibling(txtNode, "previousSibling");
                                  mergeSibling(txtNode, "nextSibling");
                                  fillData = txtNode;
                                  this.setStart(txtNode, browser.webkit ? 1 : 0).collapse(true);
                              }
                          }
                          var nativeRange = this.document.createRange();
                          if (this.collapsed && browser.opera && this.startContainer.nodeType == 1) {
                              var child = this.startContainer.childNodes[this.startOffset];
                              if (!child) {
                                  //往前靠拢
                                  child = this.startContainer.lastChild;
                                  if (child && domUtils.isBr(child)) {
                                      this.setStartBefore(child).collapse(true);
                                  }
                              } else {
                                  //向后靠拢
                                  while (child && domUtils.isBlockElm(child)) {
                                      if (child.nodeType == 1 && child.childNodes[0]) {
                                          child = child.childNodes[0];
                                      } else {
                                          break;
                                      }
                                  }
                                  child && this.setStartBefore(child).collapse(true);
                              }
                          }
                          //是createAddress最后一位算的不准，现在这里进行微调
                          checkOffset(this);
                          nativeRange.setStart(this.startContainer, this.startOffset);
                          nativeRange.setEnd(this.endContainer, this.endOffset);
                          sel.addRange(nativeRange);
                      }
                      return this;
                  },

            createAddress: function (ignoreEnd, ignoreTxt) {
                var addr = {},
                    me = this;

                function getAddress(isStart) {
                    var node = isStart ? me.startContainer : me.endContainer;
                    var parents = domUtils.findParents(node, true, function (node) {
                            return !domUtils.isBody(node);
                        }),
                        addrs = [];
                    for (var i = 0, ci; (ci = parents[i++]); ) {
                        addrs.push(domUtils.getNodeIndex(ci, ignoreTxt));
                    }
                    var firstIndex = 0;

                    if (ignoreTxt) {
                        if (node.nodeType == 3) {
                            var tmpNode = node.previousSibling;
                            while (tmpNode && tmpNode.nodeType == 3) {
                                firstIndex += tmpNode.nodeValue.replace(fillCharReg, "").length;
                                tmpNode = tmpNode.previousSibling;
                            }
                            firstIndex += isStart ? me.startOffset : me.endOffset; // - (fillCharReg.test(node.nodeValue) ? 1 : 0 )
                        } else {
                            node = node.childNodes[isStart ? me.startOffset : me.endOffset];
                            if (node) {
                                firstIndex = domUtils.getNodeIndex(node, ignoreTxt);
                            } else {
                                node = isStart ? me.startContainer : me.endContainer;
                                var first = node.firstChild;
                                while (first) {
                                    if (domUtils.isFillChar(first)) {
                                        first = first.nextSibling;
                                        continue;
                                    }
                                    firstIndex++;
                                    if (first.nodeType == 3) {
                                        while (first && first.nodeType == 3) {
                                            first = first.nextSibling;
                                        }
                                    } else {
                                        first = first.nextSibling;
                                    }
                                }
                            }
                        }
                    } else {
                        firstIndex = isStart ? (domUtils.isFillChar(node) ? 0 : me.startOffset) : me.endOffset;
                    }
                    if (firstIndex < 0) {
                        firstIndex = 0;
                    }
                    addrs.push(firstIndex);
                    return addrs;
                }
                addr.startAddress = getAddress(true);
                if (!ignoreEnd) {
                    addr.endAddress = me.collapsed ? [].concat(addr.startAddress) : getAddress();
                }
                return addr;
            },
            moveToAddress: function (addr, ignoreEnd) {
                var me = this;
                function getNode(address, isStart) {
                    var tmpNode = me.body,
                        parentNode,
                        offset;
                    for (var i = 0, ci, l = address.length; i < l; i++) {
                        ci = address[i];
                        parentNode = tmpNode;
                        tmpNode = tmpNode.childNodes[ci];
                        if (!tmpNode) {
                            offset = ci;
                            break;
                        }
                    }
                    if (isStart) {
                        if (tmpNode) {
                            me.setStartBefore(tmpNode);
                        } else {
                            me.setStart(parentNode, offset);
                        }
                    } else {
                        if (tmpNode) {
                            me.setEndBefore(tmpNode);
                        } else {
                            me.setEnd(parentNode, offset);
                        }
                    }
                }
                getNode(addr.startAddress, true);
                !ignoreEnd && addr.endAddress && getNode(addr.endAddress);
                return me;
            },
            equals: function (rng) {
                for (var p in this) {
                    if (this.hasOwnProperty(p)) {
                        if (this[p] !== rng[p]) return false;
                    }
                }
                return true;
            },
            scrollIntoView: function () {
                var $span = $('<span style="padding:0;margin:0;display:block;border:0">&nbsp;</span>');
                this.cloneRange().insertNode($span.get(0));
                var winScrollTop = $(window).scrollTop(),
                    winHeight = $(window).height(),
                    spanTop = $span.offset().top;
                if (spanTop < winScrollTop - winHeight || spanTop > winScrollTop + winHeight) {
                    if (spanTop > winScrollTop + winHeight) {
                        window.scrollTo(0, spanTop - winHeight + $span.height());
                    } else {
                        window.scrollTo(0, winScrollTop - spanTop);
                    }
                }
                $span.remove();
            },
            getOffset: function () {
                var bk = this.createBookmark();
                var offset = $(bk.start).css("display", "inline-block").offset();
                this.moveToBookmark(bk);
                return offset;
            },
        };
    })();
    ///import editor.js
    ///import core/browser.js
    ///import core/dom/dom.js
    ///import core/dom/dtd.js
    ///import core/dom/domUtils.js
    ///import core/dom/Range.js
    /**
     * @class UM.dom.Selection    Selection类
     */
    (function () {
        function getBoundaryInformation(range, start) {
            var getIndex = domUtils.getNodeIndex;
            range = range.duplicate();
            range.collapse(start);
            var parent = range.parentElement();
            //如果节点里没有子节点，直接退出
            if (!parent.hasChildNodes()) {
                return { container: parent, offset: 0 };
            }
            var siblings = parent.children,
                child,
                testRange = range.duplicate(),
                startIndex = 0,
                endIndex = siblings.length - 1,
                index = -1,
                distance;
            while (startIndex <= endIndex) {
                index = Math.floor((startIndex + endIndex) / 2);
                child = siblings[index];
                testRange.moveToElementText(child);
                var position = testRange.compareEndPoints("StartToStart", range);
                if (position > 0) {
                    endIndex = index - 1;
                } else if (position < 0) {
                    startIndex = index + 1;
                } else {
                    //trace:1043
                    return { container: parent, offset: getIndex(child) };
                }
            }
            if (index == -1) {
                testRange.moveToElementText(parent);
                testRange.setEndPoint("StartToStart", range);
                distance = testRange.text.replace(/(\r\n|\r)/g, "\n").length;
                siblings = parent.childNodes;
                if (!distance) {
                    child = siblings[siblings.length - 1];
                    return { container: child, offset: child.nodeValue.length };
                }

                var i = siblings.length;
                while (distance > 0) {
                    distance -= siblings[--i].nodeValue.length;
                }
                return { container: siblings[i], offset: -distance };
            }
            testRange.collapse(position > 0);
            testRange.setEndPoint(position > 0 ? "StartToStart" : "EndToStart", range);
            distance = testRange.text.replace(/(\r\n|\r)/g, "\n").length;
            if (!distance) {
                return dtd.$empty[child.tagName] || dtd.$nonChild[child.tagName]
                    ? { container: parent, offset: getIndex(child) + (position > 0 ? 0 : 1) }
                    : { container: child, offset: position > 0 ? 0 : child.childNodes.length };
            }
            while (distance > 0) {
                try {
                    var pre = child;
                    child = child[position > 0 ? "previousSibling" : "nextSibling"];
                    distance -= child.nodeValue.length;
                } catch (e) {
                    return { container: parent, offset: getIndex(pre) };
                }
            }
            return { container: child, offset: position > 0 ? -distance : child.nodeValue.length + distance };
        }

        /**
         * 将ieRange转换为Range对象
         * @param {Range}   ieRange    ieRange对象
         * @param {Range}   range      Range对象
         * @return  {Range}  range       返回转换后的Range对象
         */
        function transformIERangeToRange(ieRange, range) {
            if (ieRange.item) {
                range.selectNode(ieRange.item(0));
            } else {
                var bi = getBoundaryInformation(ieRange, true);
                range.setStart(bi.container, bi.offset);
                if (ieRange.compareEndPoints("StartToEnd", ieRange) != 0) {
                    bi = getBoundaryInformation(ieRange, false);
                    range.setEnd(bi.container, bi.offset);
                }
            }
            return range;
        }

        /**
         * 获得ieRange
         * @param {Selection} sel    Selection对象
         * @return {ieRange}    得到ieRange
         */
        function _getIERange(sel, txtRange) {
            var ieRange;
            //ie下有可能报错
            try {
                ieRange = sel.getNative(txtRange).createRange();
            } catch (e) {
                return null;
            }
            var el = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
            if ((el.ownerDocument || el) === sel.document) {
                return ieRange;
            }
            return null;
        }

        var Selection = (dom.Selection = function (doc, body) {
            var me = this;
            me.document = doc;
            me.body = body;
            if (browser.ie9below) {
                $(body)
                    .on("beforedeactivate", function () {
                        me._bakIERange = me.getIERange();
                    })
                    .on("activate", function () {
                        try {
                            var ieNativRng = _getIERange(me);
                            if ((!ieNativRng || !me.rangeInBody(ieNativRng)) && me._bakIERange) {
                                me._bakIERange.select();
                            }
                        } catch (ex) {}
                        me._bakIERange = null;
                    });
            }
        });

        Selection.prototype = {
            hasNativeRange: function () {
                var rng;
                if (!browser.ie || browser.ie9above) {
                    var nativeSel = this.getNative();
                    if (!nativeSel.rangeCount) {
                        return false;
                    }
                    rng = nativeSel.getRangeAt(0);
                } else {
                    rng = _getIERange(this);
                }
                return this.rangeInBody(rng);
            },
            /**
             * 获取原生seleciton对象
             * @public
             * @function
             * @name    UM.dom.Selection.getNative
             * @return {Selection}    获得selection对象
             */
            getNative: function (txtRange) {
                var doc = this.document;
                try {
                    return !doc ? null : browser.ie9below || txtRange ? doc.selection : domUtils.getWindow(doc).getSelection();
                } catch (e) {
                    return null;
                }
            },
            /**
             * 获得ieRange
             * @public
             * @function
             * @name    UM.dom.Selection.getIERange
             * @return {ieRange}    返回ie原生的Range
             */
            getIERange: function (txtRange) {
                var ieRange = _getIERange(this, txtRange);
                if (!ieRange || !this.rangeInBody(ieRange, txtRange)) {
                    if (this._bakIERange) {
                        return this._bakIERange;
                    }
                }
                return ieRange;
            },
            rangeInBody: function (rng, txtRange) {
                var node = browser.ie9below || txtRange ? (rng.item ? rng.item() : rng.parentElement()) : rng.startContainer;

                return node === this.body || domUtils.inDoc(node, this.body);
            },
            /**
             * 缓存当前选区的range和选区的开始节点
             * @public
             * @function
             * @name    UM.dom.Selection.cache
             */
            cache: function () {
                this.clear();
                this._cachedRange = this.getRange();
                this._cachedStartElement = this.getStart();
                this._cachedStartElementPath = this.getStartElementPath();
            },

            getStartElementPath: function () {
                if (this._cachedStartElementPath) {
                    return this._cachedStartElementPath;
                }
                var start = this.getStart();
                if (start) {
                    return domUtils.findParents(start, true, null, true);
                }
                return [];
            },
            /**
             * 清空缓存
             * @public
             * @function
             * @name    UM.dom.Selection.clear
             */
            clear: function () {
                this._cachedStartElementPath = this._cachedRange = this._cachedStartElement = null;
            },
            /**
             * 编辑器是否得到了选区
             */
            isFocus: function () {
                return this.hasNativeRange();
            },
            /**
             * 获取选区对应的Range
             * @public
             * @function
             * @name    UM.dom.Selection.getRange
             * @returns {UM.dom.Range}    得到Range对象
             */
            getRange: function () {
                var me = this;
                function optimze(range) {
                    var child = me.body.firstChild,
                        collapsed = range.collapsed;
                    while (child && child.firstChild) {
                        range.setStart(child, 0);
                        child = child.firstChild;
                    }
                    if (!range.startContainer) {
                        range.setStart(me.body, 0);
                    }
                    if (collapsed) {
                        range.collapse(true);
                    }
                }

                if (me._cachedRange != null) {
                    return this._cachedRange;
                }
                var range = new dom.Range(me.document, me.body);
                if (browser.ie9below) {
                    var nativeRange = me.getIERange();
                    if (nativeRange && this.rangeInBody(nativeRange)) {
                        try {
                            transformIERangeToRange(nativeRange, range);
                        } catch (e) {
                            optimze(range);
                        }
                    } else {
                        optimze(range);
                    }
                } else {
                    var sel = me.getNative();
                    if (sel && sel.rangeCount && me.rangeInBody(sel.getRangeAt(0))) {
                        var firstRange = sel.getRangeAt(0);
                        var lastRange = sel.getRangeAt(sel.rangeCount - 1);
                        range.setStart(firstRange.startContainer, firstRange.startOffset).setEnd(lastRange.endContainer, lastRange.endOffset);
                        if (range.collapsed && domUtils.isBody(range.startContainer) && !range.startOffset) {
                            optimze(range);
                        }
                    } else {
                        //trace:1734 有可能已经不在dom树上了，标识的节点
                        if (this._bakRange && (this._bakRange.startContainer === this.body || domUtils.inDoc(this._bakRange.startContainer, this.body))) {
                            return this._bakRange;
                        }
                        optimze(range);
                    }
                }

                return (this._bakRange = range);
            },

            /**
             * 获取开始元素，用于状态反射
             * @public
             * @function
             * @name    UM.dom.Selection.getStart
             * @return {Element}     获得开始元素
             */
            getStart: function () {
                if (this._cachedStartElement) {
                    return this._cachedStartElement;
                }
                var range = browser.ie9below ? this.getIERange() : this.getRange(),
                    tmpRange,
                    start,
                    tmp,
                    parent;
                if (browser.ie9below) {
                    if (!range) {
                        //todo 给第一个值可能会有问题
                        return this.document.body.firstChild;
                    }
                    //control元素
                    if (range.item) {
                        return range.item(0);
                    }
                    tmpRange = range.duplicate();
                    //修正ie下<b>x</b>[xx] 闭合后 <b>x|</b>xx
                    tmpRange.text.length > 0 && tmpRange.moveStart("character", 1);
                    tmpRange.collapse(1);
                    start = tmpRange.parentElement();
                    parent = tmp = range.parentElement();
                    while ((tmp = tmp.parentNode)) {
                        if (tmp == start) {
                            start = parent;
                            break;
                        }
                    }
                } else {
                    start = range.startContainer;
                    if (start.nodeType == 1 && start.hasChildNodes()) {
                        start = start.childNodes[Math.min(start.childNodes.length - 1, range.startOffset)];
                    }
                    if (start.nodeType == 3) {
                        return start.parentNode;
                    }
                }
                return start;
            },
            /**
             * 得到选区中的文本
             * @public
             * @function
             * @name    UM.dom.Selection.getText
             * @return  {String}    选区中包含的文本
             */
            getText: function () {
                var nativeSel, nativeRange;
                if (this.isFocus() && (nativeSel = this.getNative())) {
                    nativeRange = browser.ie9below ? nativeSel.createRange() : nativeSel.getRangeAt(0);
                    return browser.ie9below ? nativeRange.text : nativeRange.toString();
                }
                return "";
            },
        };
    })();
    /**
     * @file
     * @name UM.Editor
     * @short Editor
     * @import editor.js,core/utils.js,core/EventBase.js,core/browser.js,core/dom/dtd.js,core/dom/domUtils.js,core/dom/Range.js,core/dom/Selection.js,plugins/serialize.js
     * @desc 编辑器主类，包含编辑器提供的大部分公用接口
     */
    (function () {
        var uid = 0,
            _selectionChangeTimer;

        /**
         * @private
         * @ignore
         * @param form  编辑器所在的form元素
         * @param editor  编辑器实例对象
         */
        function setValue(form, editor) {
            var textarea;
            if (editor.textarea) {
                if (utils.isString(editor.textarea)) {
                    for (var i = 0, ti, tis = domUtils.getElementsByTagName(form, "textarea"); (ti = tis[i++]); ) {
                        if (ti.id == "umeditor_textarea_" + editor.options.textarea) {
                            textarea = ti;
                            break;
                        }
                    }
                } else {
                    textarea = editor.textarea;
                }
            }
            if (!textarea) {
                form.appendChild(
                    (textarea = domUtils.createElement(document, "textarea", {
                        name: editor.options.textarea,
                        id: "umeditor_textarea_" + editor.options.textarea,
                        style: "display:none",
                    }))
                );
                //不要产生多个textarea
                editor.textarea = textarea;
            }
            textarea.value = editor.hasContents() ? (editor.options.allHtmlEnabled ? editor.getAllHtml() : editor.getContent(null, null, true)) : "";
        }
        function loadPlugins(me) {
            //初始化插件
            for (var pi in UM.plugins) {
                if (me.options.excludePlugins.indexOf(pi) == -1) {
                    UM.plugins[pi].call(me);
                    me.plugins[pi] = 1;
                }
            }
            me.langIsReady = true;

            me.fireEvent("langReady");
        }
        function checkCurLang(I18N) {
            for (var lang in I18N) {
                return lang;
            }
        }
        /**
         * UEditor编辑器类
         * @name Editor
         * @desc 创建一个跟编辑器实例
         * - ***container*** 编辑器容器对象
         * - ***iframe*** 编辑区域所在的iframe对象
         * - ***window*** 编辑区域所在的window
         * - ***document*** 编辑区域所在的document对象
         * - ***body*** 编辑区域所在的body对象
         * - ***selection*** 编辑区域的选区对象
         */
        var Editor = (UM.Editor = function (options) {
            var me = this;
            me.uid = uid++;
            EventBase.call(me);
            me.commands = {};
            me.options = utils.extend(utils.clone(options || {}), UMEDITOR_CONFIG, true);
            me.shortcutkeys = {};
            me.inputRules = [];
            me.outputRules = [];
            //设置默认的常用属性
            me.setOpt({
                isShow: true,
                initialContent: "",
                initialStyle: "",
                autoClearinitialContent: false,
                textarea: "editorValue",
                focus: false,
                focusInEnd: true,
                autoClearEmptyNode: true,
                fullscreen: false,
                readonly: false,
                zIndex: 999,
                enterTag: "p",
                lang: "zh-cn",
                langPath: me.options.UMEDITOR_HOME_URL + "lang/",
                theme: "default",
                themePath: me.options.UMEDITOR_HOME_URL + "themes/",
                allHtmlEnabled: false,
                autoSyncData: true,
                autoHeightEnabled: true,
                excludePlugins: "",
            });
            me.plugins = {};
            if (!utils.isEmptyObject(UM.I18N)) {
                //修改默认的语言类型
                me.options.lang = checkCurLang(UM.I18N);
                loadPlugins(me);
            } else {
                utils.loadFile(
                    document,
                    {
                        src: me.options.langPath + me.options.lang + "/" + me.options.lang + ".js",
                        tag: "script",
                        type: "text/javascript",
                        defer: "defer",
                    },
                    function () {
                        loadPlugins(me);
                    }
                );
            }
        });
        Editor.prototype = {
            /**
             * 当编辑器ready后执行传入的fn,如果编辑器已经完成ready，就马上执行fn，fn的中的this是编辑器实例。
             * 大部分的实例接口都需要放在该方法内部执行，否则在IE下可能会报错。
             * @name ready
             * @grammar editor.ready(fn) fn是当编辑器渲染好后执行的function
             * @example
             * var editor = new UM.ui.Editor();
             * editor.render("myEditor");
             * editor.ready(function(){
             *     editor.setContent("欢迎使用UEditor！");
             * })
             */
            ready: function (fn) {
                var me = this;
                if (fn) {
                    me.isReady ? fn.apply(me) : me.addListener("ready", fn);
                }
            },
            /**
             * 为编辑器设置默认参数值。若用户配置为空，则以默认配置为准
             * @grammar editor.setOpt(key,value);      //传入一个键、值对
             * @grammar editor.setOpt({ key:value});   //传入一个json对象
             */
            setOpt: function (key, val) {
                var obj = {};
                if (utils.isString(key)) {
                    obj[key] = val;
                } else {
                    obj = key;
                }
                utils.extend(this.options, obj, true);
            },
            getOpt: function (key) {
                return this.options[key] || "";
            },
            /**
             * 销毁编辑器实例对象
             * @name destroy
             * @grammar editor.destroy();
             */
            destroy: function () {
                var me = this;
                me.fireEvent("destroy");
                var container = me.container.parentNode;
                if (container === document.body) {
                    container = me.container;
                }
                var textarea = me.textarea;
                if (!textarea) {
                    textarea = document.createElement("textarea");
                    container.parentNode.insertBefore(textarea, container);
                } else {
                    textarea.style.display = "";
                }

                textarea.style.width = me.body.offsetWidth + "px";
                textarea.style.height = me.body.offsetHeight + "px";
                textarea.value = me.getContent();
                textarea.id = me.key;
                if (container.contains(textarea)) {
                    $(textarea).insertBefore(container);
                }
                container.innerHTML = "";

                domUtils.remove(container);
                UM.clearCache(me.id);
                //trace:2004
                for (var p in me) {
                    if (me.hasOwnProperty(p)) {
                        delete this[p];
                    }
                }
            },
            initialCont: function (holder) {
                if (holder) {
                    holder.getAttribute("name") && (this.options.textarea = holder.getAttribute("name"));
                    if (holder && /script|textarea/gi.test(holder.tagName)) {
                        var newDiv = document.createElement("div");
                        holder.parentNode.insertBefore(newDiv, holder);
                        this.options.initialContent = UM.htmlparser(holder.value || holder.innerHTML || this.options.initialContent).toHtml();
                        holder.className && (newDiv.className = holder.className);
                        holder.style.cssText && (newDiv.style.cssText = holder.style.cssText);

                        if (/textarea/i.test(holder.tagName)) {
                            this.textarea = holder;
                            this.textarea.style.display = "none";
                        } else {
                            holder.parentNode.removeChild(holder);
                            holder.id && (newDiv.id = holder.id);
                        }
                        holder = newDiv;
                        holder.innerHTML = "";
                    }
                    return holder;
                } else {
                    return null;
                }
            },
            /**
             * 渲染编辑器的DOM到指定容器，必须且只能调用一次
             * @name render
             * @grammar editor.render(containerId);    //可以指定一个容器ID
             * @grammar editor.render(containerDom);   //也可以直接指定容器对象
             */
            render: function (container) {
                var me = this,
                    options = me.options,
                    getStyleValue = function (attr) {
                        return parseInt($(container).css(attr));
                    };

                if (utils.isString(container)) {
                    container = document.getElementById(container);
                }
                if (container) {
                    this.id = container.getAttribute("id");
                    UM.setEditor(this);
                    utils.cssRule("edui-style-body", me.options.initialStyle, document);

                    container = this.initialCont(container);

                    container.className += " edui-body-container";

                    if (options.initialFrameWidth) {
                        options.minFrameWidth = options.initialFrameWidth;
                    } else {
                        //都没给值，先写死了
                        options.minFrameWidth = options.initialFrameWidth = $(container).width() || UM.defaultWidth;
                    }
                    if (options.initialFrameHeight) {
                        options.minFrameHeight = options.initialFrameHeight;
                    } else {
                        options.initialFrameHeight = options.minFrameHeight = $(container).height() || UM.defaultHeight;
                    }

                    container.style.width = /%$/.test(options.initialFrameWidth)
                        ? "100%"
                        : options.initialFrameWidth - getStyleValue("padding-left") - getStyleValue("padding-right") + "px";

                    var height = /%$/.test(options.initialFrameHeight) ? "100%" : options.initialFrameHeight - getStyleValue("padding-top") - getStyleValue("padding-bottom");
                    if (this.options.autoHeightEnabled) {
                        container.style.minHeight = height + "px";
                        container.style.height = "";
                        if (browser.ie && browser.version <= 6) {
                            container.style.height = height;
                            container.style.setExpression("height", "this.scrollHeight <= " + height + ' ? "' + height + 'px" : "auto"');
                        }
                    } else {
                        $(container).height(height);
                    }
                    container.style.zIndex = options.zIndex;
                    this._setup(container);
                }
            },
            /**
             * 编辑器初始化
             * @private
             * @ignore
             * @param {Element} doc 编辑器Iframe中的文档对象
             */
            _setup: function (cont) {
                var me = this,
                    options = me.options;

                cont.contentEditable = true;
                document.body.spellcheck = false;

                me.document = document;
                me.window = document.defaultView || document.parentWindow;
                me.body = cont;
                me.$body = $(cont);
                me.selection = new dom.Selection(document, me.body);
                me._isEnabled = false;
                //gecko初始化就能得到range,无法判断isFocus了
                var geckoSel;
                if (browser.gecko && (geckoSel = this.selection.getNative())) {
                    geckoSel.removeAllRanges();
                }
                this._initEvents();
                //为form提交提供一个隐藏的textarea
                for (var form = cont.parentNode; form && !domUtils.isBody(form); form = form.parentNode) {
                    if (form.tagName == "FORM") {
                        me.form = form;
                        if (me.options.autoSyncData) {
                            $(cont).on("blur", function () {
                                setValue(form, me);
                            });
                        } else {
                            $(form).on("submit", function () {
                                setValue(this, me);
                            });
                        }
                        break;
                    }
                }
                if (options.initialContent) {
                    if (options.autoClearinitialContent) {
                        var oldExecCommand = me.execCommand;
                        me.execCommand = function () {
                            me.fireEvent("firstBeforeExecCommand");
                            return oldExecCommand.apply(me, arguments);
                        };
                        this._setDefaultContent(options.initialContent);
                    } else this.setContent(options.initialContent, false, true);
                }

                //编辑器不能为空内容

                if (domUtils.isEmptyNode(me.body)) {
                    me.body.innerHTML = "<p>" + (browser.ie ? "" : "<br/>") + "</p>";
                }
                //如果要求focus, 就把光标定位到内容开始
                if (options.focus) {
                    setTimeout(function () {
                        me.focus(me.options.focusInEnd);
                        //如果自动清除开着，就不需要做selectionchange;
                        !me.options.autoClearinitialContent && me._selectionChange();
                    }, 0);
                }
                if (!me.container) {
                    me.container = cont.parentNode;
                }

                me._bindshortcutKeys();
                me.isReady = 1;
                me.fireEvent("ready");
                options.onready && options.onready.call(me);
                if (!browser.ie || browser.ie9above) {
                    $(me.body).on("blur focus", function (e) {
                        var nSel = me.selection.getNative();
                        //chrome下会出现alt+tab切换时，导致选区位置不对
                        if (e.type == "blur") {
                            if (nSel.rangeCount > 0) {
                                me._bakRange = nSel.getRangeAt(0);
                            }
                        } else {
                            try {
                                me._bakRange && nSel.addRange(me._bakRange);
                            } catch (e) {}
                            me._bakRange = null;
                        }
                    });
                }

                !options.isShow && me.setHide();
                options.readonly && me.setDisabled();
            },
            /**
             * 同步编辑器的数据，为提交数据做准备，主要用于你是手动提交的情况
             * @name sync
             * @grammar editor.sync(); //从编辑器的容器向上查找，如果找到就同步数据
             * @grammar editor.sync(formID); //formID制定一个要同步数据的form的id,编辑器的数据会同步到你指定form下
             * @desc
             * 后台取得数据得键值使用你容器上得''name''属性，如果没有就使用参数传入的''textarea''
             * @example
             * editor.sync();
             * form.sumbit(); //form变量已经指向了form元素
             *
             */
            sync: function (formId) {
                var me = this,
                    form = formId
                        ? document.getElementById(formId)
                        : domUtils.findParent(
                              me.body.parentNode,
                              function (node) {
                                  return node.tagName == "FORM";
                              },
                              true
                          );
                form && setValue(form, me);
            },
            /**
             * 设置编辑器高度
             * @name setHeight
             * @grammar editor.setHeight(number);  //纯数值，不带单位
             */
            setHeight: function (height, notSetHeight) {
                !notSetHeight && (this.options.initialFrameHeight = height);
                if (this.options.autoHeightEnabled) {
                    $(this.body).css({
                        "min-height": height + "px",
                    });
                    if (browser.ie && browser.version <= 6 && this.container) {
                        this.container.style.height = height;
                        this.container.style.setExpression("height", "this.scrollHeight <= " + height + ' ? "' + height + 'px" : "auto"');
                    }
                } else {
                    $(this.body).height(height);
                }
                this.fireEvent("resize");
            },
            /**
             * 设置编辑器宽度
             * @name setWidth
             * @grammar editor.setWidth(number);  //纯数值，不带单位
             */
            setWidth: function (width) {
                this.$container && this.$container.width(width);
                $(this.body).width(width - $(this.body).css("padding-left").replace("px", "") * 1 - $(this.body).css("padding-right").replace("px", "") * 1);
                this.fireEvent("resize");
            },
            addshortcutkey: function (cmd, keys) {
                var obj = {};
                if (keys) {
                    obj[cmd] = keys;
                } else {
                    obj = cmd;
                }
                utils.extend(this.shortcutkeys, obj);
            },
            _bindshortcutKeys: function () {
                var me = this,
                    shortcutkeys = this.shortcutkeys;
                me.addListener("keydown", function (type, e) {
                    var keyCode = e.keyCode || e.which;
                    for (var i in shortcutkeys) {
                        var tmp = shortcutkeys[i].split(",");
                        for (var t = 0, ti; (ti = tmp[t++]); ) {
                            ti = ti.split(":");
                            var key = ti[0],
                                param = ti[1];
                            if (/^(ctrl)(\+shift)?\+(\d+)$/.test(key.toLowerCase()) || /^(\d+)$/.test(key)) {
                                if (
                                    ((RegExp.$1 == "ctrl" ? e.ctrlKey || e.metaKey : 0) && (RegExp.$2 != "" ? e[RegExp.$2.slice(1) + "Key"] : 1) && keyCode == RegExp.$3) ||
                                    keyCode == RegExp.$1
                                ) {
                                    if (me.queryCommandState(i, param) != -1) me.execCommand(i, param);
                                    domUtils.preventDefault(e);
                                }
                            }
                        }
                    }
                });
            },
            /**
             * 获取编辑器内容
             * @name getContent
             * @grammar editor.getContent()  => String //若编辑器中只包含字符"&lt;p&gt;&lt;br /&gt;&lt;/p/&gt;"会返回空。
             * @grammar editor.getContent(fn)  => String
             * @example
             * getContent默认是会现调用hasContents来判断编辑器是否为空，如果是，就直接返回空字符串
             * 你也可以传入一个fn来接替hasContents的工作，定制判断的规则
             * editor.getContent(function(){
             *     return false //编辑器没有内容 ，getContent直接返回空
             * })
             */
            getContent: function (cmd, fn, notSetCursor, ignoreBlank, formatter) {
                var me = this;
                if (cmd && utils.isFunction(cmd)) {
                    fn = cmd;
                    cmd = "";
                }
                if (fn ? !fn() : !this.hasContents()) {
                    return "";
                }
                me.fireEvent("beforegetcontent");
                var root = UM.htmlparser(me.body.innerHTML, ignoreBlank);
                me.filterOutputRule(root);
                me.fireEvent("aftergetcontent", root);
                return root.toHtml(formatter);
            },
            /**
             * 取得完整的html代码，可以直接显示成完整的html文档
             * @name getAllHtml
             * @grammar editor.getAllHtml()  => String
             */
            getAllHtml: function () {
                var me = this,
                    headHtml = [],
                    html = "";
                me.fireEvent("getAllHtml", headHtml);
                if (browser.ie && browser.version > 8) {
                    var headHtmlForIE9 = "";
                    utils.each(me.document.styleSheets, function (si) {
                        headHtmlForIE9 += si.href ? '<link rel="stylesheet" type="text/css" href="' + si.href + '" />' : "<style>" + si.cssText + "</style>";
                    });
                    utils.each(me.document.getElementsByTagName("script"), function (si) {
                        headHtmlForIE9 += si.outerHTML;
                    });
                }
                return (
                    "<html><head>" +
                    (me.options.charset ? '<meta http-equiv="Content-Type" content="text/html; charset=' + me.options.charset + '"/>' : "") +
                    (headHtmlForIE9 || me.document.getElementsByTagName("head")[0].innerHTML) +
                    headHtml.join("\n") +
                    "</head>" +
                    "<body " +
                    (ie && browser.version < 9 ? 'class="view"' : "") +
                    ">" +
                    me.getContent(null, null, true) +
                    "</body></html>"
                );
            },
            /**
             * 得到编辑器的纯文本内容，但会保留段落格式
             * @name getPlainTxt
             * @grammar editor.getPlainTxt()  => String
             */
            getPlainTxt: function () {
                var reg = new RegExp(domUtils.fillChar, "g"),
                    html = this.body.innerHTML.replace(/[\n\r]/g, ""); //ie要先去了\n在处理
                html = html
                    .replace(/<(p|div)[^>]*>(<br\/?>|&nbsp;)<\/\1>/gi, "\n")
                    .replace(/<br\/?>/gi, "\n")
                    .replace(/<[^>/]+>/g, "")
                    .replace(/(\n)?<\/([^>]+)>/g, function (a, b, c) {
                        return dtd.$block[c] ? "\n" : b ? b : "";
                    });
                //取出来的空格会有c2a0会变成乱码，处理这种情况\u00a0
                return html
                    .replace(reg, "")
                    .replace(/\u00a0/g, " ")
                    .replace(/&nbsp;/g, " ");
            },

            /**
             * 获取编辑器中的纯文本内容,没有段落格式
             * @name getContentTxt
             * @grammar editor.getContentTxt()  => String
             */
            getContentTxt: function () {
                var reg = new RegExp(domUtils.fillChar, "g");
                //取出来的空格会有c2a0会变成乱码，处理这种情况\u00a0
                return this.body[browser.ie ? "innerText" : "textContent"].replace(reg, "").replace(/\u00a0/g, " ");
            },

            /**
             * 将html设置到编辑器中, 如果是用于初始化时给编辑器赋初值，则必须放在ready方法内部执行
             * @name setContent
             * @grammar editor.setContent(html)
             * @example
             * var editor = new UM.ui.Editor()
             * editor.ready(function(){
             *     //需要ready后执行，否则可能报错
             *     editor.setContent("欢迎使用UEditor！");
             * })
             */
            setContent: function (html, isAppendTo, notFireSelectionchange) {
                var me = this;

                me.fireEvent("beforesetcontent", html);
                var root = UM.htmlparser(html);
                me.filterInputRule(root);
                html = root.toHtml();

                me.body.innerHTML = (isAppendTo ? me.body.innerHTML : "") + html;

                function isCdataDiv(node) {
                    return node.tagName == "DIV" && node.getAttribute("cdata_tag");
                }
                //给文本或者inline节点套p标签
                if (me.options.enterTag == "p") {
                    var child = this.body.firstChild,
                        tmpNode;
                    if (!child || (child.nodeType == 1 && (dtd.$cdata[child.tagName] || isCdataDiv(child) || domUtils.isCustomeNode(child)) && child === this.body.lastChild)) {
                        this.body.innerHTML = "<p>" + (browser.ie ? "&nbsp;" : "<br/>") + "</p>" + this.body.innerHTML;
                    } else {
                        var p = me.document.createElement("p");
                        while (child) {
                            while (child && (child.nodeType == 3 || (child.nodeType == 1 && dtd.p[child.tagName] && !dtd.$cdata[child.tagName]))) {
                                tmpNode = child.nextSibling;
                                p.appendChild(child);
                                child = tmpNode;
                            }
                            if (p.firstChild) {
                                if (!child) {
                                    me.body.appendChild(p);
                                    break;
                                } else {
                                    child.parentNode.insertBefore(p, child);
                                    p = me.document.createElement("p");
                                }
                            }
                            child = child.nextSibling;
                        }
                    }
                }
                me.fireEvent("aftersetcontent");
                me.fireEvent("contentchange");

                !notFireSelectionchange && me._selectionChange();
                //清除保存的选区
                me._bakRange = me._bakIERange = me._bakNativeRange = null;
                //trace:1742 setContent后gecko能得到焦点问题
                var geckoSel;
                if (browser.gecko && (geckoSel = this.selection.getNative())) {
                    geckoSel.removeAllRanges();
                }
                if (me.options.autoSyncData) {
                    me.form && setValue(me.form, me);
                }
            },

            /**
             * 让编辑器获得焦点，toEnd确定focus位置
             * @name focus
             * @grammar editor.focus([toEnd])   //默认focus到编辑器头部，toEnd为true时focus到内容尾部
             */
            focus: function (toEnd) {
                try {
                    var me = this,
                        rng = me.selection.getRange();
                    if (toEnd) {
                        rng.setStartAtLast(me.body.lastChild).setCursor(false, true);
                    } else {
                        rng.select(true);
                    }
                    this.fireEvent("focus");
                } catch (e) {}
            },
            /**
             * 使编辑区域失去焦点
             */
            blur: function () {
                var sel = this.selection.getNative();
                sel.empty ? sel.empty() : sel.removeAllRanges();
                this.fireEvent("blur");
            },
            /**
             * 判断编辑器当前是否获得了焦点
             */
            isFocus: function () {
                if (this.fireEvent("isfocus") === true) {
                    return true;
                }
                return this.selection.isFocus();
            },

            /**
             * 初始化UE事件及部分事件代理
             * @private
             * @ignore
             */
            _initEvents: function () {
                var me = this,
                    cont = me.body,
                    _proxyDomEvent = function () {
                        me._proxyDomEvent.apply(me, arguments);
                    };

                $(cont)
                    .on("click contextmenu mousedown keydown keyup keypress mouseup mouseover mouseout selectstart", _proxyDomEvent)
                    .on("focus blur", _proxyDomEvent)
                    .on("mouseup keydown", function (evt) {
                        //特殊键不触发selectionchange
                        if (evt.type == "keydown" && (evt.ctrlKey || evt.metaKey || evt.shiftKey || evt.altKey)) {
                            return;
                        }
                        if (evt.button == 2) return;
                        me._selectionChange(250, evt);
                    });
            },
            /**
             * 触发事件代理
             * @private
             * @ignore
             */
            _proxyDomEvent: function (evt) {
                return this.fireEvent(evt.type.replace(/^on/, ""), evt);
            },
            /**
             * 变化选区
             * @private
             * @ignore
             */
            _selectionChange: function (delay, evt) {
                var me = this;
                //有光标才做selectionchange 为了解决未focus时点击source不能触发更改工具栏状态的问题（source命令notNeedUndo=1）
                //            if ( !me.selection.isFocus() ){
                //                return;
                //            }

                var hackForMouseUp = false;
                var mouseX, mouseY;
                if (browser.ie && browser.version < 9 && evt && evt.type == "mouseup") {
                    var range = this.selection.getRange();
                    if (!range.collapsed) {
                        hackForMouseUp = true;
                        mouseX = evt.clientX;
                        mouseY = evt.clientY;
                    }
                }
                clearTimeout(_selectionChangeTimer);
                _selectionChangeTimer = setTimeout(function () {
                    if (!me.selection.getNative()) {
                        return;
                    }
                    //修复一个IE下的bug: 鼠标点击一段已选择的文本中间时，可能在mouseup后的一段时间内取到的range是在selection的type为None下的错误值.
                    //IE下如果用户是拖拽一段已选择文本，则不会触发mouseup事件，所以这里的特殊处理不会对其有影响
                    var ieRange;
                    if (hackForMouseUp && me.selection.getNative().type == "None") {
                        ieRange = me.document.body.createTextRange();
                        try {
                            ieRange.moveToPoint(mouseX, mouseY);
                        } catch (ex) {
                            ieRange = null;
                        }
                    }
                    var bakGetIERange;
                    if (ieRange) {
                        bakGetIERange = me.selection.getIERange;
                        me.selection.getIERange = function () {
                            return ieRange;
                        };
                    }
                    me.selection.cache();
                    if (bakGetIERange) {
                        me.selection.getIERange = bakGetIERange;
                    }
                    if (me.selection._cachedRange && me.selection._cachedStartElement) {
                        me.fireEvent("beforeselectionchange");
                        // 第二个参数causeByUi为true代表由用户交互造成的selectionchange.
                        me.fireEvent("selectionchange", !!evt);
                        me.fireEvent("afterselectionchange");
                        me.selection.clear();
                    }
                }, delay || 50);
            },
            _callCmdFn: function (fnName, args) {
                args = Array.prototype.slice.call(args, 0);
                var cmdName = args.shift().toLowerCase(),
                    cmd,
                    cmdFn;
                cmd = this.commands[cmdName] || UM.commands[cmdName];
                cmdFn = cmd && cmd[fnName];
                //没有querycommandstate或者没有command的都默认返回0
                if ((!cmd || !cmdFn) && fnName == "queryCommandState") {
                    return 0;
                } else if (cmdFn) {
                    return cmdFn.apply(this, [cmdName].concat(args));
                }
            },

            /**
             * 执行编辑命令cmdName，完成富文本编辑效果
             * @name execCommand
             * @grammar editor.execCommand(cmdName)   => {*}
             */
            execCommand: function (cmdName) {
                if (!this.isFocus()) {
                    var bakRange = this.selection._bakRange;
                    if (bakRange) {
                        bakRange.select();
                    } else {
                        this.focus(true);
                    }
                }
                cmdName = cmdName.toLowerCase();
                var me = this,
                    result,
                    cmd = me.commands[cmdName] || UM.commands[cmdName];
                if (!cmd || !cmd.execCommand) {
                    return null;
                }
                if (!cmd.notNeedUndo && !me.__hasEnterExecCommand) {
                    me.__hasEnterExecCommand = true;
                    if (me.queryCommandState.apply(me, arguments) != -1) {
                        me.fireEvent("saveScene");
                        me.fireEvent("beforeexeccommand", cmdName);
                        result = this._callCmdFn("execCommand", arguments);
                        !cmd.ignoreContentChange && !me._ignoreContentChange && me.fireEvent("contentchange");
                        me.fireEvent("afterexeccommand", cmdName);
                        me.fireEvent("saveScene");
                    }
                    me.__hasEnterExecCommand = false;
                } else {
                    result = this._callCmdFn("execCommand", arguments);
                    !me.__hasEnterExecCommand && !cmd.ignoreContentChange && !me._ignoreContentChange && me.fireEvent("contentchange");
                }
                !me.__hasEnterExecCommand && !cmd.ignoreContentChange && !me._ignoreContentChange && me._selectionChange();
                return result;
            },
            /**
             * 根据传入的command命令，查选编辑器当前的选区，返回命令的状态
             * @name  queryCommandState
             * @grammar editor.queryCommandState(cmdName)  => (-1|0|1)
             * @desc
             * * ''-1'' 当前命令不可用
             * * ''0'' 当前命令可用
             * * ''1'' 当前命令已经执行过了
             */
            queryCommandState: function (cmdName) {
                try {
                    return this._callCmdFn("queryCommandState", arguments);
                } catch (e) {
                    return 0;
                }
            },

            /**
             * 根据传入的command命令，查选编辑器当前的选区，根据命令返回相关的值
             * @name  queryCommandValue
             * @grammar editor.queryCommandValue(cmdName)  =>  {*}
             */
            queryCommandValue: function (cmdName) {
                try {
                    return this._callCmdFn("queryCommandValue", arguments);
                } catch (e) {
                    return null;
                }
            },
            /**
             * 检查编辑区域中是否有内容，若包含tags中的节点类型，直接返回true
             * @name  hasContents
             * @desc
             * 默认有文本内容，或者有以下节点都不认为是空
             * <code>{table:1,ul:1,ol:1,dl:1,iframe:1,area:1,base:1,col:1,hr:1,img:1,embed:1,input:1,link:1,meta:1,param:1}</code>
             * @grammar editor.hasContents()  => (true|false)
             * @grammar editor.hasContents(tags)  =>  (true|false)  //若文档中包含tags数组里对应的tag，直接返回true
             * @example
             * editor.hasContents(['span']) //如果编辑器里有这些，不认为是空
             */
            hasContents: function (tags) {
                if (tags) {
                    for (var i = 0, ci; (ci = tags[i++]); ) {
                        if (this.body.getElementsByTagName(ci).length > 0) {
                            return true;
                        }
                    }
                }
                if (!domUtils.isEmptyBlock(this.body)) {
                    return true;
                }
                //随时添加,定义的特殊标签如果存在，不能认为是空
                tags = ["div"];
                for (i = 0; (ci = tags[i++]); ) {
                    var nodes = domUtils.getElementsByTagName(this.body, ci);
                    for (var n = 0, cn; (cn = nodes[n++]); ) {
                        if (domUtils.isCustomeNode(cn)) {
                            return true;
                        }
                    }
                }
                return false;
            },
            /**
             * 重置编辑器，可用来做多个tab使用同一个编辑器实例
             * @name  reset
             * @desc
             * * 清空编辑器内容
             * * 清空回退列表
             * @grammar editor.reset()
             */
            reset: function () {
                this.fireEvent("reset");
            },
            isEnabled: function () {
                return this._isEnabled != true;
            },

            setEnabled: function () {
                var me = this,
                    range;

                me.body.contentEditable = true;

                /* 恢复选区 */
                if (me.lastBk) {
                    range = me.selection.getRange();
                    try {
                        range.moveToBookmark(me.lastBk);
                        delete me.lastBk;
                    } catch (e) {
                        range.setStartAtFirst(me.body).collapse(true);
                    }
                    range.select(true);
                }

                /* 恢复query函数 */
                if (me.bkqueryCommandState) {
                    me.queryCommandState = me.bkqueryCommandState;
                    delete me.bkqueryCommandState;
                }

                /* 恢复原生事件 */
                if (me._bkproxyDomEvent) {
                    me._proxyDomEvent = me._bkproxyDomEvent;
                    delete me._bkproxyDomEvent;
                }

                /* 触发事件 */
                me.fireEvent("setEnabled");
            },
            /**
             * 设置当前编辑区域可以编辑
             * @name enable
             * @grammar editor.enable()
             */
            enable: function () {
                return this.setEnabled();
            },
            setDisabled: function (except, keepDomEvent) {
                var me = this;

                me.body.contentEditable = false;
                me._except = except ? (utils.isArray(except) ? except : [except]) : [];

                /* 备份最后的选区 */
                if (!me.lastBk) {
                    me.lastBk = me.selection.getRange().createBookmark(true);
                }

                /* 备份并重置query函数 */
                if (!me.bkqueryCommandState) {
                    me.bkqueryCommandState = me.queryCommandState;
                    me.queryCommandState = function (type) {
                        if (utils.indexOf(me._except, type) != -1) {
                            return me.bkqueryCommandState.apply(me, arguments);
                        }
                        return -1;
                    };
                }

                /* 备份并墙原生事件 */
                if (!keepDomEvent && !me._bkproxyDomEvent) {
                    me._bkproxyDomEvent = me._proxyDomEvent;
                    me._proxyDomEvent = function () {
                        return false;
                    };
                }

                /* 触发事件 */
                me.fireEvent("selectionchange");
                me.fireEvent("setDisabled", me._except);
            },
            /** 设置当前编辑区域不可编辑,except中的命令除外
             * @name disable
             * @grammar editor.disable()
             * @grammar editor.disable(except)  //例外的命令，也即即使设置了disable，此处配置的命令仍然可以执行
             * @example
             * //禁用工具栏中除加粗和插入图片之外的所有功能
             * editor.disable(['bold','insertimage']);//可以是单一的String,也可以是Array
             */
            disable: function (except) {
                return this.setDisabled(except);
            },
            /**
             * 设置默认内容
             * @ignore
             * @private
             * @param  {String} cont 要存入的内容
             */
            _setDefaultContent: (function () {
                function clear() {
                    var me = this;
                    if (me.document.getElementById("initContent")) {
                        me.body.innerHTML = "<p>" + (ie ? "" : "<br/>") + "</p>";
                        me.removeListener("firstBeforeExecCommand focus", clear);
                        setTimeout(function () {
                            me.focus();
                            me._selectionChange();
                        }, 0);
                    }
                }

                return function (cont) {
                    var me = this;
                    me.body.innerHTML = '<p id="initContent">' + cont + "</p>";

                    me.addListener("firstBeforeExecCommand focus", clear);
                };
            })(),
            /**
             * show方法的兼容版本
             * @private
             * @ignore
             */
            setShow: function () {
                var me = this,
                    range = me.selection.getRange();
                if (me.container.style.display == "none") {
                    //有可能内容丢失了
                    try {
                        range.moveToBookmark(me.lastBk);
                        delete me.lastBk;
                    } catch (e) {
                        range.setStartAtFirst(me.body).collapse(true);
                    }
                    //ie下focus实效，所以做了个延迟
                    setTimeout(function () {
                        range.select(true);
                    }, 100);
                    me.container.style.display = "";
                }
            },
            /**
             * 显示编辑器
             * @name show
             * @grammar editor.show()
             */
            show: function () {
                return this.setShow();
            },
            /**
             * hide方法的兼容版本
             * @private
             * @ignore
             */
            setHide: function () {
                var me = this;
                if (!me.lastBk) {
                    me.lastBk = me.selection.getRange().createBookmark(true);
                }
                me.container.style.display = "none";
            },
            /**
             * 隐藏编辑器
             * @name hide
             * @grammar editor.hide()
             */
            hide: function () {
                return this.setHide();
            },
            /**
             * 根据制定的路径，获取对应的语言资源
             * @name  getLang
             * @grammar editor.getLang(path)  =>  （JSON|String) 路径根据的是lang目录下的语言文件的路径结构
             * @example
             * editor.getLang('contextMenu.delete') //如果当前是中文，那返回是的是删除
             */
            getLang: function (path) {
                var lang = UM.I18N[this.options.lang];
                if (!lang) {
                    throw Error("not import language file");
                }
                path = (path || "").split(".");
                for (var i = 0, ci; (ci = path[i++]); ) {
                    lang = lang[ci];
                    if (!lang) break;
                }
                return lang;
            },
            /**
             * 计算编辑器当前内容的长度
             * @name  getContentLength
             * @grammar editor.getContentLength(ingoneHtml,tagNames)  =>
             * @example
             * editor.getLang(true)
             */
            getContentLength: function (ingoneHtml, tagNames) {
                var count = this.getContent(false, false, true).length;
                if (ingoneHtml) {
                    tagNames = (tagNames || []).concat(["hr", "img", "iframe"]);
                    count = this.getContentTxt().replace(/[\t\r\n]+/g, "").length;
                    for (var i = 0, ci; (ci = tagNames[i++]); ) {
                        count += this.body.getElementsByTagName(ci).length;
                    }
                }
                return count;
            },
            addInputRule: function (rule, ignoreUndo) {
                rule.ignoreUndo = ignoreUndo;
                this.inputRules.push(rule);
            },
            filterInputRule: function (root, isUndoLoad) {
                for (var i = 0, ci; (ci = this.inputRules[i++]); ) {
                    if (isUndoLoad && ci.ignoreUndo) {
                        continue;
                    }
                    ci.call(this, root);
                }
            },
            addOutputRule: function (rule, ignoreUndo) {
                rule.ignoreUndo = ignoreUndo;
                this.outputRules.push(rule);
            },
            filterOutputRule: function (root, isUndoLoad) {
                for (var i = 0, ci; (ci = this.outputRules[i++]); ) {
                    if (isUndoLoad && ci.ignoreUndo) {
                        continue;
                    }
                    ci.call(this, root);
                }
            },
        };
        utils.inherits(Editor, EventBase);
    })();

    /**
     * @file
     * @name UM.filterWord
     * @short filterWord
     * @desc 用来过滤word粘贴过来的字符串
     * @import editor.js,core/utils.js
     * @anthor zhanyi
     */
    var filterWord = (UM.filterWord = (function () {
        //是否是word过来的内容
        function isWordDocument(str) {
            return /(class="?Mso|style="[^"]*\bmso\-|w:WordDocument|<(v|o):|lang=)/gi.test(str);
        }
        //去掉小数
        function transUnit(v) {
            v = v.replace(/[\d.]+\w+/g, function (m) {
                return utils.transUnitToPx(m);
            });
            return v;
        }

        function filterPasteWord(str) {
            return (
                str
                    .replace(/[\t\r\n]+/g, " ")
                    .replace(/<!--[\s\S]*?-->/gi, "")
                    //转换图片
                    .replace(/<v:shape [^>]*>[\s\S]*?.<\/v:shape>/gi, function (str) {
                        //opera能自己解析出image所这里直接返回空
                        if (browser.opera) {
                            return "";
                        }
                        try {
                            //有可能是bitmap占为图，无用，直接过滤掉，主要体现在粘贴excel表格中
                            if (/Bitmap/i.test(str)) {
                                return "";
                            }
                            var width = str.match(/width:([ \d.]*p[tx])/i)[1],
                                height = str.match(/height:([ \d.]*p[tx])/i)[1],
                                src = str.match(/src=\s*"([^"]*)"/i)[1];
                            return '<img width="' + transUnit(width) + '" height="' + transUnit(height) + '" src="' + src + '" />';
                        } catch (e) {
                            return "";
                        }
                    })
                    //针对wps添加的多余标签处理
                    .replace(/<\/?div[^>]*>/g, "")
                    //去掉多余的属性
                    .replace(/v:\w+=(["']?)[^'"]+\1/g, "")
                    .replace(/<(!|script[^>]*>.*?<\/script(?=[>\s])|\/?(\?xml(:\w+)?|xml|meta|link|style|\w+:\w+)(?=[\s\/>]))[^>]*>/gi, "")
                    .replace(/<p [^>]*class="?MsoHeading"?[^>]*>(.*?)<\/p>/gi, "<p><strong>$1</strong></p>")
                    //去掉多余的属性
                    .replace(/\s+(class|lang|align)\s*=\s*(['"]?)([\w-]+)\2/gi, function (str, name, marks, val) {
                        //保留list的标示
                        return name == "class" && val == "MsoListParagraph" ? str : "";
                    })
                    //清除多余的font/span不能匹配&nbsp;有可能是空格
                    .replace(/<(font|span)[^>]*>(\s*)<\/\1>/gi, function (a, b, c) {
                        return c.replace(/[\t\r\n ]+/g, " ");
                    })
                    //处理style的问题
                    .replace(/(<[a-z][^>]*)\sstyle=(["'])([^\2]*?)\2/gi, function (str, tag, tmp, style) {
                        var n = [],
                            s = style
                                .replace(/^\s+|\s+$/, "")
                                .replace(/&#39;/g, "'")
                                .replace(/&quot;/gi, "'")
                                .split(/;\s*/g);

                        for (var i = 0, v; (v = s[i]); i++) {
                            var name,
                                value,
                                parts = v.split(":");

                            if (parts.length == 2) {
                                name = parts[0].toLowerCase();
                                value = parts[1].toLowerCase();
                                if ((/^(background)\w*/.test(name) && value.replace(/(initial|\s)/g, "").length == 0) || (/^(margin)\w*/.test(name) && /^0\w+$/.test(value))) {
                                    continue;
                                }

                                switch (name) {
                                    case "mso-padding-alt":
                                    case "mso-padding-top-alt":
                                    case "mso-padding-right-alt":
                                    case "mso-padding-bottom-alt":
                                    case "mso-padding-left-alt":
                                    case "mso-margin-alt":
                                    case "mso-margin-top-alt":
                                    case "mso-margin-right-alt":
                                    case "mso-margin-bottom-alt":
                                    case "mso-margin-left-alt":
                                    //ie下会出现挤到一起的情况
                                    //case "mso-table-layout-alt":
                                    case "mso-height":
                                    case "mso-width":
                                    case "mso-vertical-align-alt":
                                        //trace:1819 ff下会解析出padding在table上
                                        if (!/<table/.test(tag)) n[i] = name.replace(/^mso-|-alt$/g, "") + ":" + transUnit(value);
                                        continue;
                                    case "horiz-align":
                                        n[i] = "text-align:" + value;
                                        continue;

                                    case "vert-align":
                                        n[i] = "vertical-align:" + value;
                                        continue;

                                    case "font-color":
                                    case "mso-foreground":
                                        n[i] = "color:" + value;
                                        continue;

                                    case "mso-background":
                                    case "mso-highlight":
                                        n[i] = "background:" + value;
                                        continue;

                                    case "mso-default-height":
                                        n[i] = "min-height:" + transUnit(value);
                                        continue;

                                    case "mso-default-width":
                                        n[i] = "min-width:" + transUnit(value);
                                        continue;

                                    case "mso-padding-between-alt":
                                        n[i] = "border-collapse:separate;border-spacing:" + transUnit(value);
                                        continue;

                                    case "text-line-through":
                                        if (value == "single" || value == "double") {
                                            n[i] = "text-decoration:line-through";
                                        }
                                        continue;
                                    case "mso-zero-height":
                                        if (value == "yes") {
                                            n[i] = "display:none";
                                        }
                                        continue;
                                    //                                case 'background':
                                    //                                    break;
                                    case "margin":
                                        if (!/[1-9]/.test(value)) {
                                            continue;
                                        }
                                }

                                if (
                                    /^(mso|column|font-emph|lang|layout|line-break|list-image|nav|panose|punct|row|ruby|sep|size|src|tab-|table-border|text-(?:decor|trans)|top-bar|version|vnd|word-break)/.test(
                                        name
                                    ) ||
                                    (/text\-indent|padding|margin/.test(name) && /\-[\d.]+/.test(value))
                                ) {
                                    continue;
                                }

                                n[i] = name + ":" + parts[1];
                            }
                        }
                        return tag + (n.length ? ' style="' + n.join(";").replace(/;{2,}/g, ";") + '"' : "");
                    })
                    .replace(/[\d.]+(cm|pt)/g, function (str) {
                        return utils.transUnitToPx(str);
                    })
            );
        }

        return function (html) {
            return isWordDocument(html) ? filterPasteWord(html) : html;
        };
    })());
    ///import editor.js
    ///import core/utils.js
    ///import core/dom/dom.js
    ///import core/dom/dtd.js
    ///import core/htmlparser.js
    //模拟的节点类
    //by zhanyi
    (function () {
        var uNode = (UM.uNode = function (obj) {
            this.type = obj.type;
            this.data = obj.data;
            this.tagName = obj.tagName;
            this.parentNode = obj.parentNode;
            this.attrs = obj.attrs || {};
            this.children = obj.children;
        });
        var notTransAttrs = {
            href: 1,
            src: 1,
            _src: 1,
            _href: 1,
            cdata_data: 1,
        };

        var notTransTagName = {
            style: 1,
            script: 1,
        };

        var indentChar = "    ",
            breakChar = "\n";

        function insertLine(arr, current, begin) {
            arr.push(breakChar);
            return current + (begin ? 1 : -1);
        }

        function insertIndent(arr, current) {
            //插入缩进
            for (var i = 0; i < current; i++) {
                arr.push(indentChar);
            }
        }

        //创建uNode的静态方法
        //支持标签和html
        uNode.createElement = function (html) {
            if (/[<>]/.test(html)) {
                return UM.htmlparser(html).children[0];
            } else {
                return new uNode({
                    type: "element",
                    children: [],
                    tagName: html,
                });
            }
        };
        uNode.createText = function (data, noTrans) {
            return new UM.uNode({
                type: "text",
                data: noTrans ? data : utils.unhtml(data || ""),
            });
        };
        function nodeToHtml(node, arr, formatter, current) {
            switch (node.type) {
                case "root":
                    for (var i = 0, ci; (ci = node.children[i++]); ) {
                        //插入新行
                        if (formatter && ci.type == "element" && !dtd.$inlineWithA[ci.tagName] && i > 1) {
                            insertLine(arr, current, true);
                            insertIndent(arr, current);
                        }
                        nodeToHtml(ci, arr, formatter, current);
                    }
                    break;
                case "text":
                    isText(node, arr);
                    break;
                case "element":
                    isElement(node, arr, formatter, current);
                    break;
                case "comment":
                    isComment(node, arr, formatter);
            }
            return arr;
        }

        function isText(node, arr) {
            if (node.parentNode.tagName == "pre") {
                //源码模式下输入html标签，不能做转换处理，直接输出
                arr.push(node.data);
            } else {
                arr.push(notTransTagName[node.parentNode.tagName] ? utils.html(node.data) : node.data.replace(/[ ]{2}/g, " &nbsp;"));
            }
        }

        function isElement(node, arr, formatter, current) {
            var attrhtml = "";
            if (node.attrs) {
                attrhtml = [];
                var attrs = node.attrs;
                for (var a in attrs) {
                    //这里就针对
                    //<p>'<img src='http://nsclick.baidu.com/u.gif?&asdf=\"sdf&asdfasdfs;asdf'></p>
                    //这里边的\"做转换，要不用innerHTML直接被截断了，属性src
                    //有可能做的不够
                    attrhtml.push(
                        a +
                            (attrs[a] !== undefined
                                ? '="' +
                                  (notTransAttrs[a]
                                      ? utils.html(attrs[a]).replace(/["]/g, function (a) {
                                            return "&quot;";
                                        })
                                      : utils.unhtml(attrs[a])) +
                                  '"'
                                : "")
                    );
                }
                attrhtml = attrhtml.join(" ");
            }
            arr.push("<" + node.tagName + (attrhtml ? " " + attrhtml : "") + (dtd.$empty[node.tagName] ? "/" : "") + ">");
            //插入新行
            if (formatter && !dtd.$inlineWithA[node.tagName] && node.tagName != "pre") {
                if (node.children && node.children.length) {
                    current = insertLine(arr, current, true);
                    insertIndent(arr, current);
                }
            }
            if (node.children && node.children.length) {
                for (var i = 0, ci; (ci = node.children[i++]); ) {
                    if (formatter && ci.type == "element" && !dtd.$inlineWithA[ci.tagName] && i > 1) {
                        insertLine(arr, current);
                        insertIndent(arr, current);
                    }
                    nodeToHtml(ci, arr, formatter, current);
                }
            }
            if (!dtd.$empty[node.tagName]) {
                if (formatter && !dtd.$inlineWithA[node.tagName] && node.tagName != "pre") {
                    if (node.children && node.children.length) {
                        current = insertLine(arr, current);
                        insertIndent(arr, current);
                    }
                }
                arr.push("</" + node.tagName + ">");
            }
        }

        function isComment(node, arr) {
            arr.push("<!--" + node.data + "-->");
        }

        function getNodeById(root, id) {
            var node;
            if (root.type == "element" && root.getAttr("id") == id) {
                return root;
            }
            if (root.children && root.children.length) {
                for (var i = 0, ci; (ci = root.children[i++]); ) {
                    if ((node = getNodeById(ci, id))) {
                        return node;
                    }
                }
            }
        }

        function getNodesByTagName(node, tagName, arr) {
            if (node.type == "element" && node.tagName == tagName) {
                arr.push(node);
            }
            if (node.children && node.children.length) {
                for (var i = 0, ci; (ci = node.children[i++]); ) {
                    getNodesByTagName(ci, tagName, arr);
                }
            }
        }
        function nodeTraversal(root, fn) {
            if (root.children && root.children.length) {
                for (var i = 0, ci; (ci = root.children[i]); ) {
                    nodeTraversal(ci, fn);
                    //ci被替换的情况，这里就不再走 fn了
                    if (ci.parentNode) {
                        if (ci.children && ci.children.length) {
                            fn(ci);
                        }
                        if (ci.parentNode) i++;
                    }
                }
            } else {
                fn(root);
            }
        }
        uNode.prototype = {
            /**
             * 当前节点对象，转换成html文本
             * @method toHtml
             * @return { String } 返回转换后的html字符串
             * @example
             * ```javascript
             * node.toHtml();
             * ```
             */

            /**
             * 当前节点对象，转换成html文本
             * @method toHtml
             * @param { Boolean } formatter 是否格式化返回值
             * @return { String } 返回转换后的html字符串
             * @example
             * ```javascript
             * node.toHtml( true );
             * ```
             */
            toHtml: function (formatter) {
                var arr = [];
                nodeToHtml(this, arr, formatter, 0);
                return arr.join("");
            },

            /**
             * 获取节点的html内容
             * @method innerHTML
             * @warning 假如节点的type不是'element'，或节点的标签名称不在dtd列表里，直接返回当前节点
             * @return { String } 返回节点的html内容
             * @example
             * ```javascript
             * var htmlstr = node.innerHTML();
             * ```
             */

            /**
             * 设置节点的html内容
             * @method innerHTML
             * @warning 假如节点的type不是'element'，或节点的标签名称不在dtd列表里，直接返回当前节点
             * @param { String } htmlstr 传入要设置的html内容
             * @return { UM.uNode } 返回节点本身
             * @example
             * ```javascript
             * node.innerHTML('<span>text</span>');
             * ```
             */
            innerHTML: function (htmlstr) {
                if (this.type != "element" || dtd.$empty[this.tagName]) {
                    return this;
                }
                if (utils.isString(htmlstr)) {
                    if (this.children) {
                        for (var i = 0, ci; (ci = this.children[i++]); ) {
                            ci.parentNode = null;
                        }
                    }
                    this.children = [];
                    var tmpRoot = UM.htmlparser(htmlstr);
                    for (var i = 0, ci; (ci = tmpRoot.children[i++]); ) {
                        this.children.push(ci);
                        ci.parentNode = this;
                    }
                    return this;
                } else {
                    var tmpRoot = new UM.uNode({
                        type: "root",
                        children: this.children,
                    });
                    return tmpRoot.toHtml();
                }
            },

            /**
             * 获取节点的纯文本内容
             * @method innerText
             * @warning 假如节点的type不是'element'，或节点的标签名称不在dtd列表里，直接返回当前节点
             * @return { String } 返回节点的存文本内容
             * @example
             * ```javascript
             * var textStr = node.innerText();
             * ```
             */

            /**
             * 设置节点的纯文本内容
             * @method innerText
             * @warning 假如节点的type不是'element'，或节点的标签名称不在dtd列表里，直接返回当前节点
             * @param { String } textStr 传入要设置的文本内容
             * @return { UM.uNode } 返回节点本身
             * @example
             * ```javascript
             * node.innerText('<span>text</span>');
             * ```
             */
            innerText: function (textStr, noTrans) {
                if (this.type != "element" || dtd.$empty[this.tagName]) {
                    return this;
                }
                if (textStr) {
                    if (this.children) {
                        for (var i = 0, ci; (ci = this.children[i++]); ) {
                            ci.parentNode = null;
                        }
                    }
                    this.children = [];
                    this.appendChild(uNode.createText(textStr, noTrans));
                    return this;
                } else {
                    return this.toHtml().replace(/<[^>]+>/g, "");
                }
            },

            /**
             * 获取当前对象的data属性
             * @method getData
             * @return { Object } 若节点的type值是elemenet，返回空字符串，否则返回节点的data属性
             * @example
             * ```javascript
             * node.getData();
             * ```
             */
            getData: function () {
                if (this.type == "element") return "";
                return this.data;
            },

            /**
             * 获取当前节点下的第一个子节点
             * @method firstChild
             * @return { UM.uNode } 返回第一个子节点
             * @example
             * ```javascript
             * node.firstChild(); //返回第一个子节点
             * ```
             */
            firstChild: function () {
                //            if (this.type != 'element' || dtd.$empty[this.tagName]) {
                //                return this;
                //            }
                return this.children ? this.children[0] : null;
            },

            /**
             * 获取当前节点下的最后一个子节点
             * @method lastChild
             * @return { UM.uNode } 返回最后一个子节点
             * @example
             * ```javascript
             * node.lastChild(); //返回最后一个子节点
             * ```
             */
            lastChild: function () {
                //            if (this.type != 'element' || dtd.$empty[this.tagName] ) {
                //                return this;
                //            }
                return this.children ? this.children[this.children.length - 1] : null;
            },

            /**
             * 获取和当前节点有相同父亲节点的前一个节点
             * @method previousSibling
             * @return { UM.uNode } 返回前一个节点
             * @example
             * ```javascript
             * node.children[2].previousSibling(); //返回子节点node.children[1]
             * ```
             */
            previousSibling: function () {
                var parent = this.parentNode;
                for (var i = 0, ci; (ci = parent.children[i]); i++) {
                    if (ci === this) {
                        return i == 0 ? null : parent.children[i - 1];
                    }
                }
            },

            /**
             * 获取和当前节点有相同父亲节点的后一个节点
             * @method nextSibling
             * @return { UM.uNode } 返回后一个节点,找不到返回null
             * @example
             * ```javascript
             * node.children[2].nextSibling(); //如果有，返回子节点node.children[3]
             * ```
             */
            nextSibling: function () {
                var parent = this.parentNode;
                for (var i = 0, ci; (ci = parent.children[i++]); ) {
                    if (ci === this) {
                        return parent.children[i];
                    }
                }
            },

            /**
             * 用新的节点替换当前节点
             * @method replaceChild
             * @param { UM.uNode } target 要替换成该节点参数
             * @param { UM.uNode } source 要被替换掉的节点
             * @return { UM.uNode } 返回替换之后的节点对象
             * @example
             * ```javascript
             * node.replaceChild(newNode, childNode); //用newNode替换childNode,childNode是node的子节点
             * ```
             */
            replaceChild: function (target, source) {
                if (this.children) {
                    if (target.parentNode) {
                        target.parentNode.removeChild(target);
                    }
                    for (var i = 0, ci; (ci = this.children[i]); i++) {
                        if (ci === source) {
                            this.children.splice(i, 1, target);
                            source.parentNode = null;
                            target.parentNode = this;
                            return target;
                        }
                    }
                }
            },

            /**
             * 在节点的子节点列表最后位置插入一个节点
             * @method appendChild
             * @param { UM.uNode } node 要插入的节点
             * @return { UM.uNode } 返回刚插入的子节点
             * @example
             * ```javascript
             * node.appendChild( newNode ); //在node内插入子节点newNode
             * ```
             */
            appendChild: function (node) {
                if (this.type == "root" || (this.type == "element" && !dtd.$empty[this.tagName])) {
                    if (!this.children) {
                        this.children = [];
                    }
                    if (node.parentNode) {
                        node.parentNode.removeChild(node);
                    }
                    for (var i = 0, ci; (ci = this.children[i]); i++) {
                        if (ci === node) {
                            this.children.splice(i, 1);
                            break;
                        }
                    }
                    this.children.push(node);
                    node.parentNode = this;
                    return node;
                }
            },

            /**
             * 在传入节点的前面插入一个节点
             * @method insertBefore
             * @param { UM.uNode } target 要插入的节点
             * @param { UM.uNode } source 在该参数节点前面插入
             * @return { UM.uNode } 返回刚插入的子节点
             * @example
             * ```javascript
             * node.parentNode.insertBefore(newNode, node); //在node节点后面插入newNode
             * ```
             */
            insertBefore: function (target, source) {
                if (this.children) {
                    if (target.parentNode) {
                        target.parentNode.removeChild(target);
                    }
                    for (var i = 0, ci; (ci = this.children[i]); i++) {
                        if (ci === source) {
                            this.children.splice(i, 0, target);
                            target.parentNode = this;
                            return target;
                        }
                    }
                }
            },

            /**
             * 在传入节点的后面插入一个节点
             * @method insertAfter
             * @param { UM.uNode } target 要插入的节点
             * @param { UM.uNode } source 在该参数节点后面插入
             * @return { UM.uNode } 返回刚插入的子节点
             * @example
             * ```javascript
             * node.parentNode.insertAfter(newNode, node); //在node节点后面插入newNode
             * ```
             */
            insertAfter: function (target, source) {
                if (this.children) {
                    if (target.parentNode) {
                        target.parentNode.removeChild(target);
                    }
                    for (var i = 0, ci; (ci = this.children[i]); i++) {
                        if (ci === source) {
                            this.children.splice(i + 1, 0, target);
                            target.parentNode = this;
                            return target;
                        }
                    }
                }
            },

            /**
             * 从当前节点的子节点列表中，移除节点
             * @method removeChild
             * @param { UM.uNode } node 要移除的节点引用
             * @param { Boolean } keepChildren 是否保留移除节点的子节点，若传入true，自动把移除节点的子节点插入到移除的位置
             * @return { * } 返回刚移除的子节点
             * @example
             * ```javascript
             * node.removeChild(childNode,true); //在node的子节点列表中移除child节点，并且吧child的子节点插入到移除的位置
             * ```
             */
            removeChild: function (node, keepChildren) {
                if (this.children) {
                    for (var i = 0, ci; (ci = this.children[i]); i++) {
                        if (ci === node) {
                            this.children.splice(i, 1);
                            ci.parentNode = null;
                            if (keepChildren && ci.children && ci.children.length) {
                                for (var j = 0, cj; (cj = ci.children[j]); j++) {
                                    this.children.splice(i + j, 0, cj);
                                    cj.parentNode = this;
                                }
                            }
                            return ci;
                        }
                    }
                }
            },

            /**
             * 获取当前节点所代表的元素属性，即获取attrs对象下的属性值
             * @method getAttr
             * @param { String } attrName 要获取的属性名称
             * @return { * } 返回attrs对象下的属性值
             * @example
             * ```javascript
             * node.getAttr('title');
             * ```
             */
            getAttr: function (attrName) {
                return this.attrs && this.attrs[attrName.toLowerCase()];
            },

            /**
             * 设置当前节点所代表的元素属性，即设置attrs对象下的属性值
             * @method setAttr
             * @param { String } attrName 要设置的属性名称
             * @param { * } attrVal 要设置的属性值，类型视设置的属性而定
             * @return { * } 返回attrs对象下的属性值
             * @example
             * ```javascript
             * node.setAttr('title','标题');
             * ```
             */
            setAttr: function (attrName, attrVal) {
                if (!attrName) {
                    delete this.attrs;
                    return;
                }
                if (!this.attrs) {
                    this.attrs = {};
                }
                if (utils.isObject(attrName)) {
                    for (var a in attrName) {
                        if (!attrName[a]) {
                            delete this.attrs[a];
                        } else {
                            this.attrs[a.toLowerCase()] = attrName[a];
                        }
                    }
                } else {
                    if (!attrVal) {
                        delete this.attrs[attrName];
                    } else {
                        this.attrs[attrName.toLowerCase()] = attrVal;
                    }
                }
            },
            hasAttr: function (attrName) {
                var attrVal = this.getAttr(attrName);
                return attrVal !== null && attrVal !== undefined;
            },
            /**
             * 获取当前节点在父节点下的位置索引
             * @method getIndex
             * @return { Number } 返回索引数值，如果没有父节点，返回-1
             * @example
             * ```javascript
             * node.getIndex();
             * ```
             */
            getIndex: function () {
                var parent = this.parentNode;
                for (var i = 0, ci; (ci = parent.children[i]); i++) {
                    if (ci === this) {
                        return i;
                    }
                }
                return -1;
            },

            /**
             * 在当前节点下，根据id查找节点
             * @method getNodeById
             * @param { String } id 要查找的id
             * @return { UM.uNode } 返回找到的节点
             * @example
             * ```javascript
             * node.getNodeById('textId');
             * ```
             */
            getNodeById: function (id) {
                var node;
                if (this.children && this.children.length) {
                    for (var i = 0, ci; (ci = this.children[i++]); ) {
                        if ((node = getNodeById(ci, id))) {
                            return node;
                        }
                    }
                }
            },

            /**
             * 在当前节点下，根据元素名称查找节点列表
             * @method getNodesByTagName
             * @param { String } tagNames 要查找的元素名称
             * @return { Array } 返回找到的节点列表
             * @example
             * ```javascript
             * node.getNodesByTagName('span');
             * ```
             */
            getNodesByTagName: function (tagNames) {
                tagNames = utils
                    .trim(tagNames)
                    .replace(/[ ]{2,}/g, " ")
                    .split(" ");
                var arr = [],
                    me = this;
                utils.each(tagNames, function (tagName) {
                    if (me.children && me.children.length) {
                        for (var i = 0, ci; (ci = me.children[i++]); ) {
                            getNodesByTagName(ci, tagName, arr);
                        }
                    }
                });
                return arr;
            },

            /**
             * 根据样式名称，获取节点的样式值
             * @method getStyle
             * @param { String } name 要获取的样式名称
             * @return { String } 返回样式值
             * @example
             * ```javascript
             * node.getStyle('font-size');
             * ```
             */
            getStyle: function (name) {
                var cssStyle = this.getAttr("style");
                if (!cssStyle) {
                    return "";
                }
                var reg = new RegExp("(^|;)\\s*" + name + ":([^;]+)", "i");
                var match = cssStyle.match(reg);
                if (match && match[0]) {
                    return match[2];
                }
                return "";
            },

            /**
             * 给节点设置样式
             * @method setStyle
             * @param { String } name 要设置的的样式名称
             * @param { String } val 要设置的的样值
             * @example
             * ```javascript
             * node.setStyle('font-size', '12px');
             * ```
             */
            setStyle: function (name, val) {
                function exec(name, val) {
                    var reg = new RegExp("(^|;)\\s*" + name + ":([^;]+;?)", "gi");
                    cssStyle = cssStyle.replace(reg, "$1");
                    if (val) {
                        cssStyle = name + ":" + utils.unhtml(val) + ";" + cssStyle;
                    }
                }

                var cssStyle = this.getAttr("style");
                if (!cssStyle) {
                    cssStyle = "";
                }
                if (utils.isObject(name)) {
                    for (var a in name) {
                        exec(a, name[a]);
                    }
                } else {
                    exec(name, val);
                }
                this.setAttr("style", utils.trim(cssStyle));
            },
            hasClass: function (className) {
                if (this.hasAttr("class")) {
                    var classNames = this.getAttr("class").split(/\s+/),
                        hasClass = false;
                    $.each(classNames, function (key, item) {
                        if (item === className) {
                            hasClass = true;
                        }
                    });
                    return hasClass;
                } else {
                    return false;
                }
            },
            addClass: function (className) {
                var classes = null,
                    hasClass = false;

                if (this.hasAttr("class")) {
                    classes = this.getAttr("class");
                    classes = classes.split(/\s+/);

                    classes.forEach(function (item) {
                        if (item === className) {
                            hasClass = true;
                            return;
                        }
                    });

                    !hasClass && classes.push(className);

                    this.setAttr("class", classes.join(" "));
                } else {
                    this.setAttr("class", className);
                }
            },
            removeClass: function (className) {
                if (this.hasAttr("class")) {
                    var cl = this.getAttr("class");
                    cl = cl.replace(new RegExp("\\b" + className + "\\b", "g"), "");
                    this.setAttr("class", utils.trim(cl).replace(/[ ]{2,}/g, " "));
                }
            },
            /**
             * 传入一个函数，递归遍历当前节点下的所有节点
             * @method traversal
             * @param { Function } fn 遍历到节点的时，传入节点作为参数，运行此函数
             * @example
             * ```javascript
             * traversal(node, function(){
             *     console.log(node.type);
             * });
             * ```
             */
            traversal: function (fn) {
                if (this.children && this.children.length) {
                    nodeTraversal(this, fn);
                }
                return this;
            },
        };
    })();

    //html字符串转换成uNode节点
    //by zhanyi
    var htmlparser = (UM.htmlparser = function (htmlstr, ignoreBlank) {
        //todo 原来的方式  [^"'<>\/] 有\/就不能配对上 <TD vAlign=top background=../AAA.JPG> 这样的标签了
        //先去掉了，加上的原因忘了，这里先记录
        var re_tag = /<(?:(?:\/([^>]+)>)|(?:!--([\S|\s]*?)-->)|(?:([^\s\/>]+)\s*((?:(?:"[^"]*")|(?:'[^']*')|[^"'<>])*)\/?>))/g,
            re_attr = /([\w\-:.]+)(?:(?:\s*=\s*(?:(?:"([^"]*)")|(?:'([^']*)')|([^\s>]+)))|(?=\s|$))/g;

        //ie下取得的html可能会有\n存在，要去掉，在处理replace(/[\t\r\n]*/g,'');代码高量的\n不能去除
        var allowEmptyTags = {
            b: 1,
            code: 1,
            i: 1,
            u: 1,
            strike: 1,
            s: 1,
            tt: 1,
            strong: 1,
            q: 1,
            samp: 1,
            em: 1,
            span: 1,
            sub: 1,
            img: 1,
            sup: 1,
            font: 1,
            big: 1,
            small: 1,
            iframe: 1,
            a: 1,
            br: 1,
            pre: 1,
        };
        htmlstr = htmlstr.replace(new RegExp(domUtils.fillChar, "g"), "");
        if (!ignoreBlank) {
            htmlstr = htmlstr.replace(
                new RegExp("[\\r\\t\\n" + (ignoreBlank ? "" : " ") + "]*</?(\\w+)\\s*(?:[^>]*)>[\\r\\t\\n" + (ignoreBlank ? "" : " ") + "]*", "g"),
                function (a, b) {
                    //br暂时单独处理
                    if (b && allowEmptyTags[b.toLowerCase()]) {
                        return a.replace(/(^[\n\r]+)|([\n\r]+$)/g, "");
                    }
                    return a.replace(new RegExp("^[\\r\\n" + (ignoreBlank ? "" : " ") + "]+"), "").replace(new RegExp("[\\r\\n" + (ignoreBlank ? "" : " ") + "]+$"), "");
                }
            );
        }

        var notTransAttrs = {
            href: 1,
            src: 1,
        };

        var uNode = UM.uNode,
            needParentNode = {
                td: "tr",
                tr: ["tbody", "thead", "tfoot"],
                tbody: "table",
                th: "tr",
                thead: "table",
                tfoot: "table",
                caption: "table",
                li: ["ul", "ol"],
                dt: "dl",
                dd: "dl",
                option: "select",
            },
            needChild = {
                ol: "li",
                ul: "li",
            };

        function text(parent, data) {
            if (needChild[parent.tagName]) {
                var tmpNode = uNode.createElement(needChild[parent.tagName]);
                parent.appendChild(tmpNode);
                tmpNode.appendChild(uNode.createText(data));
                parent = tmpNode;
            } else {
                parent.appendChild(uNode.createText(data));
            }
        }

        function element(parent, tagName, htmlattr) {
            var needParentTag;
            if ((needParentTag = needParentNode[tagName])) {
                var tmpParent = parent,
                    hasParent;
                while (tmpParent.type != "root") {
                    if (utils.isArray(needParentTag) ? utils.indexOf(needParentTag, tmpParent.tagName) != -1 : needParentTag == tmpParent.tagName) {
                        parent = tmpParent;
                        hasParent = true;
                        break;
                    }
                    tmpParent = tmpParent.parentNode;
                }
                if (!hasParent) {
                    parent = element(parent, utils.isArray(needParentTag) ? needParentTag[0] : needParentTag);
                }
            }
            //按dtd处理嵌套
            //        if(parent.type != 'root' && !dtd[parent.tagName][tagName])
            //            parent = parent.parentNode;
            var elm = new uNode({
                parentNode: parent,
                type: "element",
                tagName: tagName.toLowerCase(),
                //是自闭合的处理一下
                children: dtd.$empty[tagName] ? null : [],
            });
            //如果属性存在，处理属性
            if (htmlattr) {
                var attrs = {},
                    match;
                while ((match = re_attr.exec(htmlattr))) {
                    attrs[match[1].toLowerCase()] = notTransAttrs[match[1].toLowerCase()] ? match[2] || match[3] || match[4] : utils.unhtml(match[2] || match[3] || match[4]);
                }
                elm.attrs = attrs;
            }

            parent.children.push(elm);
            //如果是自闭合节点返回父亲节点
            return dtd.$empty[tagName] ? parent : elm;
        }

        function comment(parent, data) {
            parent.children.push(
                new uNode({
                    type: "comment",
                    data: data,
                    parentNode: parent,
                })
            );
        }

        var match,
            currentIndex = 0,
            nextIndex = 0;
        //设置根节点
        var root = new uNode({
            type: "root",
            children: [],
        });
        var currentParent = root;

        while ((match = re_tag.exec(htmlstr))) {
            currentIndex = match.index;
            try {
                if (currentIndex > nextIndex) {
                    //text node
                    text(currentParent, htmlstr.slice(nextIndex, currentIndex));
                }
                if (match[3]) {
                    if (dtd.$cdata[currentParent.tagName]) {
                        text(currentParent, match[0]);
                    } else {
                        //start tag
                        currentParent = element(currentParent, match[3].toLowerCase(), match[4]);
                    }
                } else if (match[1]) {
                    if (currentParent.type != "root") {
                        if (dtd.$cdata[currentParent.tagName] && !dtd.$cdata[match[1]]) {
                            text(currentParent, match[0]);
                        } else {
                            var tmpParent = currentParent;
                            while (currentParent.type == "element" && currentParent.tagName != match[1].toLowerCase()) {
                                currentParent = currentParent.parentNode;
                                if (currentParent.type == "root") {
                                    currentParent = tmpParent;
                                    throw "break";
                                }
                            }
                            //end tag
                            currentParent = currentParent.parentNode;
                        }
                    }
                } else if (match[2]) {
                    //comment
                    comment(currentParent, match[2]);
                }
            } catch (e) {}

            nextIndex = re_tag.lastIndex;
        }
        //如果结束是文本，就有可能丢掉，所以这里手动判断一下
        //例如 <li>sdfsdfsdf<li>sdfsdfsdfsdf
        if (nextIndex < htmlstr.length) {
            text(currentParent, htmlstr.slice(nextIndex));
        }
        return root;
    });
    /**
     * @file
     * @name UM.filterNode
     * @short filterNode
     * @desc 根据给定的规则过滤节点
     * @import editor.js,core/utils.js
     * @anthor zhanyi
     */
    var filterNode = (UM.filterNode = (function () {
        function filterNode(node, rules) {
            switch (node.type) {
                case "text":
                    break;
                case "element":
                    var val;
                    if ((val = rules[node.tagName])) {
                        if (val === "-") {
                            node.parentNode.removeChild(node);
                        } else if (utils.isFunction(val)) {
                            var parentNode = node.parentNode,
                                index = node.getIndex();
                            val(node);
                            if (node.parentNode) {
                                if (node.children) {
                                    for (var i = 0, ci; (ci = node.children[i]); ) {
                                        filterNode(ci, rules);
                                        if (ci.parentNode) {
                                            i++;
                                        }
                                    }
                                }
                            } else {
                                for (var i = index, ci; (ci = parentNode.children[i]); ) {
                                    filterNode(ci, rules);
                                    if (ci.parentNode) {
                                        i++;
                                    }
                                }
                            }
                        } else {
                            var attrs = val["$"];
                            if (attrs && node.attrs) {
                                var tmpAttrs = {},
                                    tmpVal;
                                for (var a in attrs) {
                                    tmpVal = node.getAttr(a);
                                    //todo 只先对style单独处理
                                    if (a == "style" && utils.isArray(attrs[a])) {
                                        var tmpCssStyle = [];
                                        utils.each(attrs[a], function (v) {
                                            var tmp;
                                            if ((tmp = node.getStyle(v))) {
                                                tmpCssStyle.push(v + ":" + tmp);
                                            }
                                        });
                                        tmpVal = tmpCssStyle.join(";");
                                    }
                                    if (tmpVal) {
                                        tmpAttrs[a] = tmpVal;
                                    }
                                }
                                node.attrs = tmpAttrs;
                            }
                            if (node.children) {
                                for (var i = 0, ci; (ci = node.children[i]); ) {
                                    filterNode(ci, rules);
                                    if (ci.parentNode) {
                                        i++;
                                    }
                                }
                            }
                        }
                    } else {
                        //如果不在名单里扣出子节点并删除该节点,cdata除外
                        if (dtd.$cdata[node.tagName]) {
                            node.parentNode.removeChild(node);
                        } else {
                            var parentNode = node.parentNode,
                                index = node.getIndex();
                            node.parentNode.removeChild(node, true);
                            for (var i = index, ci; (ci = parentNode.children[i]); ) {
                                filterNode(ci, rules);
                                if (ci.parentNode) {
                                    i++;
                                }
                            }
                        }
                    }
                    break;
                case "comment":
                    node.parentNode.removeChild(node);
            }
        }
        return function (root, rules) {
            if (utils.isEmptyObject(rules)) {
                return root;
            }
            var val;
            if ((val = rules["-"])) {
                utils.each(val.split(" "), function (k) {
                    rules[k] = "-";
                });
            }
            for (var i = 0, ci; (ci = root.children[i]); ) {
                filterNode(ci, rules);
                if (ci.parentNode) {
                    i++;
                }
            }
            return root;
        };
    })());
    ///import core
    /**
     * @description 插入内容
     * @name baidu.editor.execCommand
     * @param   {String}   cmdName     inserthtml插入内容的命令
     * @param   {String}   html                要插入的内容
     * @author zhanyi
     */
    UM.commands["inserthtml"] = {
        execCommand: function (command, html, notNeedFilter) {
            var me = this,
                range,
                div;
            if (!html) {
                return;
            }
            if (me.fireEvent("beforeinserthtml", html) === true) {
                return;
            }
            range = me.selection.getRange();
            div = range.document.createElement("div");
            div.style.display = "inline";

            if (!notNeedFilter) {
                var root = UM.htmlparser(html);
                //如果给了过滤规则就先进行过滤
                if (me.options.filterRules) {
                    UM.filterNode(root, me.options.filterRules);
                }
                //执行默认的处理
                me.filterInputRule(root);
                html = root.toHtml();
            }
            div.innerHTML = utils.trim(html);

            if (!range.collapsed) {
                var tmpNode = range.startContainer;
                if (domUtils.isFillChar(tmpNode)) {
                    range.setStartBefore(tmpNode);
                }
                tmpNode = range.endContainer;
                if (domUtils.isFillChar(tmpNode)) {
                    range.setEndAfter(tmpNode);
                }
                range.txtToElmBoundary();
                //结束边界可能放到了br的前边，要把br包含进来
                // x[xxx]<br/>
                if (range.endContainer && range.endContainer.nodeType == 1) {
                    tmpNode = range.endContainer.childNodes[range.endOffset];
                    if (tmpNode && domUtils.isBr(tmpNode)) {
                        range.setEndAfter(tmpNode);
                    }
                }
                if (range.startOffset == 0) {
                    tmpNode = range.startContainer;
                    if (domUtils.isBoundaryNode(tmpNode, "firstChild")) {
                        tmpNode = range.endContainer;
                        if (range.endOffset == (tmpNode.nodeType == 3 ? tmpNode.nodeValue.length : tmpNode.childNodes.length) && domUtils.isBoundaryNode(tmpNode, "lastChild")) {
                            me.body.innerHTML = "<p>" + (browser.ie ? "" : "<br/>") + "</p>";
                            range.setStart(me.body.firstChild, 0).collapse(true);
                        }
                    }
                }
                !range.collapsed && range.deleteContents();
                if (range.startContainer.nodeType == 1) {
                    var child = range.startContainer.childNodes[range.startOffset],
                        pre;
                    if (child && domUtils.isBlockElm(child) && (pre = child.previousSibling) && domUtils.isBlockElm(pre)) {
                        range.setEnd(pre, pre.childNodes.length).collapse();
                        while (child.firstChild) {
                            pre.appendChild(child.firstChild);
                        }
                        domUtils.remove(child);
                    }
                }
            }

            var child,
                parent,
                pre,
                tmp,
                hadBreak = 0,
                nextNode;
            //如果当前位置选中了fillchar要干掉，要不会产生空行
            if (range.inFillChar()) {
                child = range.startContainer;
                if (domUtils.isFillChar(child)) {
                    range.setStartBefore(child).collapse(true);
                    domUtils.remove(child);
                } else if (domUtils.isFillChar(child, true)) {
                    child.nodeValue = child.nodeValue.replace(fillCharReg, "");
                    range.startOffset--;
                    range.collapsed && range.collapse(true);
                }
            }
            while ((child = div.firstChild)) {
                if (hadBreak) {
                    var p = me.document.createElement("p");
                    while (child && (child.nodeType == 3 || !dtd.$block[child.tagName])) {
                        nextNode = child.nextSibling;
                        p.appendChild(child);
                        child = nextNode;
                    }
                    if (p.firstChild) {
                        child = p;
                    }
                }
                range.insertNode(child);
                nextNode = child.nextSibling;
                if (!hadBreak && child.nodeType == domUtils.NODE_ELEMENT && domUtils.isBlockElm(child)) {
                    parent = domUtils.findParent(child, function (node) {
                        return domUtils.isBlockElm(node);
                    });
                    if (parent && parent.tagName.toLowerCase() != "body" && !(dtd[parent.tagName][child.nodeName] && child.parentNode === parent)) {
                        if (!dtd[parent.tagName][child.nodeName]) {
                            pre = parent;
                        } else {
                            tmp = child.parentNode;
                            while (tmp !== parent) {
                                pre = tmp;
                                tmp = tmp.parentNode;
                            }
                        }

                        domUtils.breakParent(child, pre || tmp);
                        //去掉break后前一个多余的节点  <p>|<[p> ==> <p></p><div></div><p>|</p>
                        var pre = child.previousSibling;
                        domUtils.trimWhiteTextNode(pre);
                        if (!pre.childNodes.length) {
                            domUtils.remove(pre);
                        }
                        //trace:2012,在非ie的情况，切开后剩下的节点有可能不能点入光标添加br占位

                        if (!browser.ie && (next = child.nextSibling) && domUtils.isBlockElm(next) && next.lastChild && !domUtils.isBr(next.lastChild)) {
                            next.appendChild(me.document.createElement("br"));
                        }
                        hadBreak = 1;
                    }
                }
                var next = child.nextSibling;
                if (!div.firstChild && next && domUtils.isBlockElm(next)) {
                    range.setStart(next, 0).collapse(true);
                    break;
                }
                range.setEndAfter(child).collapse();
            }

            child = range.startContainer;

            if (nextNode && domUtils.isBr(nextNode)) {
                domUtils.remove(nextNode);
            }
            //用chrome可能有空白展位符
            if (domUtils.isBlockElm(child) && domUtils.isEmptyNode(child)) {
                if ((nextNode = child.nextSibling)) {
                    domUtils.remove(child);
                    if (nextNode.nodeType == 1 && dtd.$block[nextNode.tagName]) {
                        range.setStart(nextNode, 0).collapse(true).shrinkBoundary();
                    }
                } else {
                    try {
                        child.innerHTML = browser.ie ? domUtils.fillChar : "<br/>";
                    } catch (e) {
                        range.setStartBefore(child);
                        domUtils.remove(child);
                    }
                }
            }
            //加上true因为在删除表情等时会删两次，第一次是删的fillData
            try {
                if (browser.ie9below && range.startContainer.nodeType == 1 && !range.startContainer.childNodes[range.startOffset]) {
                    var start = range.startContainer,
                        pre = start.childNodes[range.startOffset - 1];
                    if (pre && pre.nodeType == 1 && dtd.$empty[pre.tagName]) {
                        var txt = this.document.createTextNode(domUtils.fillChar);
                        range.insertNode(txt).setStart(txt, 0).collapse(true);
                    }
                }
                setTimeout(function () {
                    range.select(true);
                });
            } catch (e) {}

            setTimeout(function () {
                range = me.selection.getRange();
                range.scrollIntoView();
                me.fireEvent("afterinserthtml");
            }, 200);
        },
    };

    ///import core
    ///import plugins\inserthtml.js
    ///commands 插入图片，操作图片的对齐方式
    ///commandsName  InsertImage,ImageNone,ImageLeft,ImageRight,ImageCenter
    ///commandsTitle  图片,默认,居左,居右,居中
    ///commandsDialog  dialogs\image
    /**
     * Created by .
     * User: zhanyi
     * for image
     */
    UM.commands["insertimage"] = {
        execCommand: function (cmd, opt) {
            opt = utils.isArray(opt) ? opt : [opt];
            if (!opt.length) {
                return;
            }
            var me = this;
            var html = [],
                str = "",
                ci;
            ci = opt[0];
            if (opt.length == 1) {
                str =
                    '<img src="' +
                    ci.src +
                    '" ' +
                    (ci._src ? ' _src="' + ci._src + '" ' : "") +
                    (ci.width ? 'width="' + ci.width + '" ' : "") +
                    (ci.height ? ' height="' + ci.height + '" ' : "") +
                    (ci["floatStyle"] == "left" || ci["floatStyle"] == "right" ? ' style="float:' + ci["floatStyle"] + ';"' : "") +
                    (ci.title && ci.title != "" ? ' title="' + ci.title + '"' : "") +
                    (ci.border && ci.border != "0" ? ' border="' + ci.border + '"' : "") +
                    (ci.alt && ci.alt != "" ? ' alt="' + ci.alt + '"' : "") +
                    (ci.hspace && ci.hspace != "0" ? ' hspace = "' + ci.hspace + '"' : "") +
                    (ci.vspace && ci.vspace != "0" ? ' vspace = "' + ci.vspace + '"' : "") +
                    "/>";
                if (ci["floatStyle"] == "center") {
                    str = '<p style="text-align: center">' + str + "</p>";
                }
                html.push(str);
            } else {
                for (var i = 0; (ci = opt[i++]); ) {
                    str =
                        "<p " +
                        (ci["floatStyle"] == "center" ? 'style="text-align: center" ' : "") +
                        '><img src="' +
                        ci.src +
                        '" ' +
                        (ci.width ? 'width="' + ci.width + '" ' : "") +
                        (ci._src ? ' _src="' + ci._src + '" ' : "") +
                        (ci.height ? ' height="' + ci.height + '" ' : "") +
                        ' style="' +
                        (ci["floatStyle"] && ci["floatStyle"] != "center" ? "float:" + ci["floatStyle"] + ";" : "") +
                        (ci.border || "") +
                        '" ' +
                        (ci.title ? ' title="' + ci.title + '"' : "") +
                        " /></p>";
                    html.push(str);
                }
            }

            me.execCommand("insertHtml", html.join(""), true);
        },
    };
    ///import core
    ///commands 段落格式,居左,居右,居中,两端对齐
    ///commandsName  JustifyLeft,JustifyCenter,JustifyRight,JustifyJustify
    ///commandsTitle  居左对齐,居中对齐,居右对齐,两端对齐
    /**
     * @description 居左右中
     * @name UM.execCommand
     * @param   {String}   cmdName     justify执行对齐方式的命令
     * @param   {String}   align               对齐方式：left居左，right居右，center居中，justify两端对齐
     * @author zhanyi
     */
    UM.plugins["justify"] = function () {
        var me = this;
        $.each("justifyleft justifyright justifycenter justifyfull".split(" "), function (i, cmdName) {
            me.commands[cmdName] = {
                execCommand: function (cmdName) {
                    return this.document.execCommand(cmdName);
                },
                queryCommandValue: function (cmdName) {
                    var val = this.document.queryCommandValue(cmdName);
                    return val === true || val === "true" ? cmdName.replace(/justify/, "") : "";
                },
                queryCommandState: function (cmdName) {
                    return this.document.queryCommandState(cmdName) ? 1 : 0;
                },
            };
        });
    };

    ///import core
    ///import plugins\removeformat.js
    ///commands 字体颜色,背景色,字号,字体,下划线,删除线
    ///commandsName  ForeColor,BackColor,FontSize,FontFamily,Underline,StrikeThrough
    ///commandsTitle  字体颜色,背景色,字号,字体,下划线,删除线
    /**
     * @description 字体
     * @name UM.execCommand
     * @param {String}     cmdName    执行的功能名称
     * @param {String}    value             传入的值
     */
    UM.plugins["font"] = function () {
        var me = this,
            fonts = {
                forecolor: "forecolor",
                backcolor: "backcolor",
                fontsize: "fontsize",
                fontfamily: "fontname",
            },
            cmdNameToStyle = {
                forecolor: "color",
                backcolor: "background-color",
                fontsize: "font-size",
                fontfamily: "font-family",
            },
            cmdNameToAttr = {
                forecolor: "color",
                fontsize: "size",
                fontfamily: "face",
            };
        me.setOpt({
            fontfamily: [
                { name: "songti", val: "宋体,SimSun" },
                { name: "yahei", val: "微软雅黑,Microsoft YaHei" },
                { name: "kaiti", val: "楷体,楷体_GB2312, SimKai" },
                { name: "heiti", val: "黑体, SimHei" },
                { name: "lishu", val: "隶书, SimLi" },
                { name: "andaleMono", val: "andale mono" },
                { name: "arial", val: "arial, helvetica,sans-serif" },
                { name: "arialBlack", val: "arial black,avant garde" },
                { name: "comicSansMs", val: "comic sans ms" },
                { name: "impact", val: "impact,chicago" },
                { name: "timesNewRoman", val: "times new roman" },
                { name: "sans-serif", val: "sans-serif" },
            ],
            fontsize: [10, 12, 16, 18, 24, 32, 48],
        });

        me.addOutputRule(function (root) {
            utils.each(root.getNodesByTagName("font"), function (node) {
                if (node.tagName == "font") {
                    var cssStyle = [];
                    for (var p in node.attrs) {
                        switch (p) {
                            case "size":
                                var val = node.attrs[p];
                                $.each(
                                    {
                                        10: "1",
                                        12: "2",
                                        16: "3",
                                        18: "4",
                                        24: "5",
                                        32: "6",
                                        48: "7",
                                    },
                                    function (k, v) {
                                        if (v == val) {
                                            val = k;
                                            return false;
                                        }
                                    }
                                );
                                cssStyle.push("font-size:" + val + "px");
                                break;
                            case "color":
                                cssStyle.push("color:" + node.attrs[p]);
                                break;
                            case "face":
                                cssStyle.push("font-family:" + node.attrs[p]);
                                break;
                            case "style":
                                cssStyle.push(node.attrs[p]);
                        }
                    }
                    node.attrs = {
                        style: cssStyle.join(";"),
                    };
                }
                node.tagName = "span";
                if (node.parentNode.tagName == "span" && node.parentNode.children.length == 1) {
                    $.each(node.attrs, function (k, v) {
                        node.parentNode.attrs[k] = k == "style" ? node.parentNode.attrs[k] + v : v;
                    });
                    node.parentNode.removeChild(node, true);
                }
            });
        });
        for (var p in fonts) {
            (function (cmd) {
                me.commands[cmd] = {
                    execCommand: function (cmdName, value) {
                        if (value == "transparent") {
                            return;
                        }
                        var rng = this.selection.getRange();
                        if (rng.collapsed) {
                            var span = $("<span></span>").css(cmdNameToStyle[cmdName], value)[0];
                            rng.insertNode(span).setStart(span, 0).setCursor();
                        } else {
                            if (cmdName == "fontsize") {
                                value = {
                                    10: "1",
                                    12: "2",
                                    16: "3",
                                    18: "4",
                                    24: "5",
                                    32: "6",
                                    48: "7",
                                }[(value + "").replace(/px/, "")];
                            }
                            this.document.execCommand(fonts[cmdName], false, value);
                            if (browser.gecko) {
                                $.each(this.$body.find("a"), function (i, a) {
                                    var parent = a.parentNode;
                                    if (parent.lastChild === parent.firstChild && /FONT|SPAN/.test(parent.tagName)) {
                                        var cloneNode = parent.cloneNode(false);
                                        cloneNode.innerHTML = a.innerHTML;
                                        $(a).html("").append(cloneNode).insertBefore(parent);

                                        $(parent).remove();
                                    }
                                });
                            }
                            if (!browser.ie) {
                                var nativeRange = this.selection.getNative().getRangeAt(0);
                                var common = nativeRange.commonAncestorContainer;
                                var rng = this.selection.getRange(),
                                    bk = rng.createBookmark(true);

                                $(common)
                                    .find("a")
                                    .each(function (i, n) {
                                        var parent = n.parentNode;
                                        if (parent.nodeName == "FONT") {
                                            var font = parent.cloneNode(false);
                                            font.innerHTML = n.innerHTML;
                                            $(n).html("").append(font);
                                        }
                                    });
                                rng.moveToBookmark(bk).select();
                            }
                            return true;
                        }
                    },
                    queryCommandValue: function (cmdName) {
                        var start = me.selection.getStart();
                        var val = $(start).css(cmdNameToStyle[cmdName]);
                        if (val === undefined) {
                            val = $(start).attr(cmdNameToAttr[cmdName]);
                        }
                        return val ? utils.fixColor(cmdName, val).replace(/px/, "") : "";
                    },
                    queryCommandState: function (cmdName) {
                        return this.queryCommandValue(cmdName);
                    },
                };
            })(p);
        }
    };
    ///import core
    ///commands 超链接,取消链接
    ///commandsName  Link,Unlink
    ///commandsTitle  超链接,取消链接
    ///commandsDialog  dialogs\link
    /**
     * 超链接
     * @function
     * @name UM.execCommand
     * @param   {String}   cmdName     link插入超链接
     * @param   {Object}  options         url地址，title标题，target是否打开新页
     * @author zhanyi
     */
    /**
     * 取消链接
     * @function
     * @name UM.execCommand
     * @param   {String}   cmdName     unlink取消链接
     * @author zhanyi
     */

    UM.plugins["link"] = function () {
        var me = this;

        me.setOpt("autourldetectinie", false);
        //在ie下禁用autolink
        if (browser.ie && this.options.autourldetectinie === false) {
            this.addListener("keyup", function (cmd, evt) {
                var me = this,
                    keyCode = evt.keyCode;
                if (keyCode == 13 || keyCode == 32) {
                    var rng = me.selection.getRange();
                    var start = rng.startContainer;
                    if (keyCode == 13) {
                        if (start.nodeName == "P") {
                            var pre = start.previousSibling;
                            if (pre && pre.nodeType == 1) {
                                var pre = pre.lastChild;
                                if (pre && pre.nodeName == "A" && !pre.getAttribute("_href")) {
                                    domUtils.remove(pre, true);
                                }
                            }
                        }
                    } else if (keyCode == 32) {
                        if (start.nodeType == 3 && /^\s$/.test(start.nodeValue)) {
                            start = start.previousSibling;
                            if (start && start.nodeName == "A" && !start.getAttribute("_href")) {
                                domUtils.remove(start, true);
                            }
                        }
                    }
                }
            });
        }

        this.addOutputRule(function (root) {
            $.each(root.getNodesByTagName("a"), function (i, a) {
                var _href = utils.html(a.getAttr("_href"));
                if (!/^(ftp|https?|\/|file)/.test(_href)) {
                    _href = "http://" + _href;
                }
                a.setAttr("href", _href);
                a.setAttr("_href");
                if (a.getAttr("title") == "") {
                    a.setAttr("title");
                }
            });
        });
        this.addInputRule(function (root) {
            $.each(root.getNodesByTagName("a"), function (i, a) {
                a.setAttr("_href", utils.html(a.getAttr("href")));
            });
        });
        me.commands["link"] = {
            execCommand: function (cmdName, opt) {
                var me = this;
                var rng = me.selection.getRange();
                if (rng.collapsed) {
                    var start = rng.startContainer;
                    if ((start = domUtils.findParentByTagName(start, "a", true))) {
                        $(start).attr(opt);
                        rng.selectNode(start).select();
                    } else {
                        rng.insertNode($("<a>" + opt.href + "</a>").attr(opt)[0]).select();
                    }
                } else {
                    me.document.execCommand("createlink", false, "_umeditor_link");
                    utils.each(
                        domUtils.getElementsByTagName(me.body, "a", function (n) {
                            return n.getAttribute("href") == "_umeditor_link";
                        }),
                        function (l) {
                            if ($(l).text() == "_umeditor_link") {
                                $(l).text(opt.href);
                            }
                            domUtils.setAttributes(l, opt);
                            rng.selectNode(l).select();
                        }
                    );
                }
            },
            queryCommandState: function () {
                return this.queryCommandValue("link") ? 1 : 0;
            },
            queryCommandValue: function () {
                var path = this.selection.getStartElementPath();
                var result;
                $.each(path, function (i, n) {
                    if (n.nodeName == "A") {
                        result = n;
                        return false;
                    }
                });
                return result;
            },
        };
        me.commands["unlink"] = {
            execCommand: function () {
                this.document.execCommand("unlink");
            },
        };
    };
    ///import core
    ///commands 打印
    ///commandsName  Print
    ///commandsTitle  打印
    /**
     * @description 打印
     * @name baidu.editor.execCommand
     * @param   {String}   cmdName     print打印编辑器内容
     * @author zhanyi
     */
    UM.commands["print"] = {
        execCommand: function () {
            var me = this,
                id = "editor_print_" + +new Date();

            $('<iframe src="" id="' + id + '" name="' + id + '" frameborder="0"></iframe>')
                .attr("id", id)
                .css({
                    width: "0px",
                    height: "0px",
                    overflow: "hidden",
                    float: "left",
                    position: "absolute",
                    top: "-10000px",
                    left: "-10000px",
                })
                .appendTo(me.$container.find(".edui-dialog-container"));

            var w = window.open("", id, ""),
                d = w.document;
            d.open();
            d.write(
                "<html><head></head><body><div>" +
                    this.getContent(null, null, true) +
                    "</div><script>" +
                    "setTimeout(function(){" +
                    "window.print();" +
                    "setTimeout(function(){" +
                    "window.parent.$('#" +
                    id +
                    "').remove();" +
                    "},100);" +
                    "},200);" +
                    "</script></body></html>"
            );
            d.close();
        },
        notNeedUndo: 1,
    };
    ///import core
    ///commands 格式
    ///commandsName  Paragraph
    ///commandsTitle  段落格式
    /**
     * 段落样式
     * @function
     * @name UM.execCommand
     * @param   {String}   cmdName     paragraph插入段落执行命令
     * @param   {String}   style               标签值为：'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'
     * @param   {String}   attrs               标签的属性
     * @author zhanyi
     */
    UM.plugins["paragraph"] = function () {
        var me = this;
        me.setOpt("paragraph", { p: "", h1: "", h2: "", h3: "", h4: "", h5: "", h6: "" });
        me.commands["paragraph"] = {
            execCommand: function (cmdName, style) {
                return this.document.execCommand("formatBlock", false, "<" + style + ">");
            },
            queryCommandValue: function () {
                try {
                    var val = this.document.queryCommandValue("formatBlock");
                } catch (e) {}
                return val;
            },
        };
    };

    ///import core
    ///import plugins\inserthtml.js
    ///commands 分割线
    ///commandsName  Horizontal
    ///commandsTitle  分隔线
    /**
     * 分割线
     * @function
     * @name UM.execCommand
     * @param {String}     cmdName    horizontal插入分割线
     */
    UM.plugins["horizontal"] = function () {
        var me = this;
        me.commands["horizontal"] = {
            execCommand: function () {
                this.document.execCommand("insertHorizontalRule");
                var rng = me.selection.getRange().txtToElmBoundary(true),
                    start = rng.startContainer;
                if (domUtils.isBody(rng.startContainer)) {
                    var next = rng.startContainer.childNodes[rng.startOffset];
                    if (!next) {
                        next = $("<p></p>")
                            .appendTo(rng.startContainer)
                            .html(browser.ie ? "&nbsp;" : "<br/>")[0];
                    }
                    rng.setStart(next, 0).setCursor();
                } else {
                    while (dtd.$inline[start.tagName] && start.lastChild === start.firstChild) {
                        var parent = start.parentNode;
                        parent.appendChild(start.firstChild);
                        parent.removeChild(start);
                        start = parent;
                    }
                    while (dtd.$inline[start.tagName]) {
                        start = start.parentNode;
                    }
                    if (start.childNodes.length == 1 && start.lastChild.nodeName == "HR") {
                        var hr = start.lastChild;
                        $(hr).insertBefore(start);
                        rng.setStart(start, 0).setCursor();
                    } else {
                        hr = $("hr", start)[0];
                        domUtils.breakParent(hr, start);
                        var pre = hr.previousSibling;
                        if (pre && domUtils.isEmptyBlock(pre)) {
                            $(pre).remove();
                        }
                        rng.setStart(hr.nextSibling, 0).setCursor();
                    }
                }
            },
        };
    };

    ///import core
    ///commands 清空文档
    ///commandsName  ClearDoc
    ///commandsTitle  清空文档
    /**
     *
     * 清空文档
     * @function
     * @name UM.execCommand
     * @param   {String}   cmdName     cleardoc清空文档
     */

    UM.commands["cleardoc"] = {
        execCommand: function () {
            var me = this,
                range = me.selection.getRange();
            me.body.innerHTML = "<p>" + (ie ? "" : "<br/>") + "</p>";
            range.setStart(me.body.firstChild, 0).setCursor(false, true);
            setTimeout(function () {
                me.fireEvent("clearDoc");
            }, 0);
        },
    };

    ///import core
    ///commands 撤销和重做
    ///commandsName  Undo,Redo
    ///commandsTitle  撤销,重做
    /**
     * @description 回退
     * @author zhanyi
     */

    UM.plugins["undo"] = function () {
        var saveSceneTimer;
        var me = this,
            maxUndoCount = me.options.maxUndoCount || 20,
            maxInputCount = me.options.maxInputCount || 20,
            fillchar = new RegExp(domUtils.fillChar + "|</hr>", "gi"); // ie会产生多余的</hr>
        var noNeedFillCharTags = {
            ol: 1,
            ul: 1,
            table: 1,
            tbody: 1,
            tr: 1,
            body: 1,
        };
        var orgState = me.options.autoClearEmptyNode;
        function compareAddr(indexA, indexB) {
            if (indexA.length != indexB.length) return 0;
            for (var i = 0, l = indexA.length; i < l; i++) {
                if (indexA[i] != indexB[i]) return 0;
            }
            return 1;
        }

        function compareRangeAddress(rngAddrA, rngAddrB) {
            if (rngAddrA.collapsed != rngAddrB.collapsed) {
                return 0;
            }
            if (!compareAddr(rngAddrA.startAddress, rngAddrB.startAddress) || !compareAddr(rngAddrA.endAddress, rngAddrB.endAddress)) {
                return 0;
            }
            return 1;
        }

        function UndoManager() {
            this.list = [];
            this.index = 0;
            this.hasUndo = false;
            this.hasRedo = false;
            this.undo = function () {
                if (this.hasUndo) {
                    if (!this.list[this.index - 1] && this.list.length == 1) {
                        this.reset();
                        return;
                    }
                    while (this.list[this.index].content == this.list[this.index - 1].content) {
                        this.index--;
                        if (this.index == 0) {
                            return this.restore(0);
                        }
                    }
                    this.restore(--this.index);
                }
            };
            this.redo = function () {
                if (this.hasRedo) {
                    while (this.list[this.index].content == this.list[this.index + 1].content) {
                        this.index++;
                        if (this.index == this.list.length - 1) {
                            return this.restore(this.index);
                        }
                    }
                    this.restore(++this.index);
                }
            };

            this.restore = function () {
                var me = this.editor;
                var scene = this.list[this.index];
                var root = UM.htmlparser(scene.content.replace(fillchar, ""));
                me.options.autoClearEmptyNode = false;
                me.filterInputRule(root, true);
                me.options.autoClearEmptyNode = orgState;
                //trace:873
                //去掉展位符
                me.body.innerHTML = root.toHtml();
                me.fireEvent("afterscencerestore");
                //处理undo后空格不展位的问题
                if (browser.ie) {
                    utils.each(domUtils.getElementsByTagName(me.document, "td th caption p"), function (node) {
                        if (domUtils.isEmptyNode(node)) {
                            domUtils.fillNode(me.document, node);
                        }
                    });
                }

                try {
                    var rng = new dom.Range(me.document, me.body).moveToAddress(scene.address);
                    if (browser.ie && rng.collapsed && rng.startContainer.nodeType == 1) {
                        var tmpNode = rng.startContainer.childNodes[rng.startOffset];
                        if (!tmpNode || (tmpNode.nodeType == 1 && dtd.$empty[tmpNode])) {
                            rng.insertNode(me.document.createTextNode(" ")).collapse(true);
                        }
                    }
                    rng.select(noNeedFillCharTags[rng.startContainer.nodeName.toLowerCase()]);
                } catch (e) {}

                this.update();
                this.clearKey();
                //不能把自己reset了
                me.fireEvent("reset", true);
            };

            this.getScene = function () {
                var me = this.editor;
                var rng = me.selection.getRange(),
                    rngAddress = rng.createAddress(false, true);
                me.fireEvent("beforegetscene");
                var root = UM.htmlparser(me.body.innerHTML, true);
                me.options.autoClearEmptyNode = false;
                me.filterOutputRule(root, true);
                me.options.autoClearEmptyNode = orgState;
                var cont = root.toHtml();
                browser.ie &&
                    (cont = cont
                        .replace(/>&nbsp;</g, "><")
                        .replace(/\s*</g, "<")
                        .replace(/>\s*/g, ">"));
                me.fireEvent("aftergetscene");
                return {
                    address: rngAddress,
                    content: cont,
                };
            };
            this.save = function (notCompareRange, notSetCursor) {
                clearTimeout(saveSceneTimer);
                var currentScene = this.getScene(notSetCursor),
                    lastScene = this.list[this.index];
                //内容相同位置相同不存
                if (lastScene && lastScene.content == currentScene.content && (notCompareRange ? 1 : compareRangeAddress(lastScene.address, currentScene.address))) {
                    return;
                }
                this.list = this.list.slice(0, this.index + 1);
                this.list.push(currentScene);
                //如果大于最大数量了，就把最前的剔除
                if (this.list.length > maxUndoCount) {
                    this.list.shift();
                }
                this.index = this.list.length - 1;
                this.clearKey();
                //跟新undo/redo状态
                this.update();
            };
            this.update = function () {
                this.hasRedo = !!this.list[this.index + 1];
                this.hasUndo = !!this.list[this.index - 1];
            };
            this.reset = function () {
                this.list = [];
                this.index = 0;
                this.hasUndo = false;
                this.hasRedo = false;
                this.clearKey();
            };
            this.clearKey = function () {
                keycont = 0;
                lastKeyCode = null;
            };
        }

        me.undoManger = new UndoManager();
        me.undoManger.editor = me;
        function saveScene() {
            this.undoManger.save();
        }

        me.addListener("saveScene", function () {
            var args = Array.prototype.splice.call(arguments, 1);
            this.undoManger.save.apply(this.undoManger, args);
        });

        me.addListener("beforeexeccommand", saveScene);
        me.addListener("afterexeccommand", saveScene);

        me.addListener("reset", function (type, exclude) {
            if (!exclude) {
                this.undoManger.reset();
            }
        });
        me.commands["redo"] = me.commands["undo"] = {
            execCommand: function (cmdName) {
                this.undoManger[cmdName]();
            },
            queryCommandState: function (cmdName) {
                return this.undoManger["has" + (cmdName.toLowerCase() == "undo" ? "Undo" : "Redo")] ? 0 : -1;
            },
            notNeedUndo: 1,
        };

        var keys = {
                //  /*Backspace*/ 8:1, /*Delete*/ 46:1,
                /*Shift*/ 16: 1,
                /*Ctrl*/ 17: 1,
                /*Alt*/ 18: 1,
                37: 1,
                38: 1,
                39: 1,
                40: 1,
            },
            keycont = 0,
            lastKeyCode;
        //输入法状态下不计算字符数
        var inputType = false;
        me.addListener("ready", function () {
            $(this.body)
                .on("compositionstart", function () {
                    inputType = true;
                })
                .on("compositionend", function () {
                    inputType = false;
                });
        });
        //快捷键
        me.addshortcutkey({
            Undo: "ctrl+90", //undo
            Redo: "ctrl+89,shift+ctrl+z", //redo
        });
        var isCollapsed = true;
        me.addListener("keydown", function (type, evt) {
            var me = this;
            var keyCode = evt.keyCode || evt.which;
            if (!keys[keyCode] && !evt.ctrlKey && !evt.metaKey && !evt.shiftKey && !evt.altKey) {
                if (inputType) return;

                if (!me.selection.getRange().collapsed) {
                    me.undoManger.save(false, true);
                    isCollapsed = false;
                    return;
                }
                if (me.undoManger.list.length == 0) {
                    me.undoManger.save(true);
                }
                clearTimeout(saveSceneTimer);
                function save(cont) {
                    if (cont.selection.getRange().collapsed) cont.fireEvent("contentchange");
                    cont.undoManger.save(false, true);
                    cont.fireEvent("selectionchange");
                }
                saveSceneTimer = setTimeout(function () {
                    if (inputType) {
                        var interalTimer = setInterval(function () {
                            if (!inputType) {
                                save(me);
                                clearInterval(interalTimer);
                            }
                        }, 300);
                        return;
                    }
                    save(me);
                }, 200);

                lastKeyCode = keyCode;
                keycont++;
                if (keycont >= maxInputCount) {
                    save(me);
                }
            }
        });
        me.addListener("keyup", function (type, evt) {
            var keyCode = evt.keyCode || evt.which;
            if (!keys[keyCode] && !evt.ctrlKey && !evt.metaKey && !evt.shiftKey && !evt.altKey) {
                if (inputType) return;
                if (!isCollapsed) {
                    this.undoManger.save(false, true);
                    isCollapsed = true;
                }
            }
        });
    };

    ///import core
    ///import plugins/inserthtml.js
    ///import plugins/undo.js
    ///import plugins/serialize.js
    ///commands 粘贴
    ///commandsName  PastePlain
    ///commandsTitle  纯文本粘贴模式
    /**
     * @description 粘贴
     * @author zhanyi
     */
    UM.plugins["paste"] = function () {
        function getClipboardData(callback) {
            var doc = this.document;
            if (doc.getElementById("baidu_pastebin")) {
                return;
            }
            var range = this.selection.getRange(),
                bk = range.createBookmark(),
                //创建剪贴的容器div
                pastebin = doc.createElement("div");
            pastebin.id = "baidu_pastebin";
            // Safari 要求div必须有内容，才能粘贴内容进来
            browser.webkit && pastebin.appendChild(doc.createTextNode(domUtils.fillChar + domUtils.fillChar));
            this.body.appendChild(pastebin);
            //trace:717 隐藏的span不能得到top
            //bk.start.innerHTML = '&nbsp;';
            bk.start.style.display = "";

            pastebin.style.cssText =
                "position:absolute;width:1px;height:1px;overflow:hidden;left:-1000px;white-space:nowrap;top:" +
                //要在现在光标平行的位置加入，否则会出现跳动的问题
                $(bk.start).position().top +
                "px";

            range.selectNodeContents(pastebin).select(true);

            setTimeout(function () {
                if (browser.webkit) {
                    for (var i = 0, pastebins = doc.querySelectorAll("#baidu_pastebin"), pi; (pi = pastebins[i++]); ) {
                        if (domUtils.isEmptyNode(pi)) {
                            domUtils.remove(pi);
                        } else {
                            pastebin = pi;
                            break;
                        }
                    }
                }
                try {
                    pastebin.parentNode.removeChild(pastebin);
                } catch (e) {}
                range.moveToBookmark(bk).select(true);
                callback(pastebin);
            }, 0);
        }

        var me = this;

        function filter(div) {
            var html;
            if (div.firstChild) {
                //去掉cut中添加的边界值
                var nodes = domUtils.getElementsByTagName(div, "span");
                for (var i = 0, ni; (ni = nodes[i++]); ) {
                    if (ni.id == "_baidu_cut_start" || ni.id == "_baidu_cut_end") {
                        domUtils.remove(ni);
                    }
                }

                if (browser.webkit) {
                    var brs = div.querySelectorAll("div br");
                    for (var i = 0, bi; (bi = brs[i++]); ) {
                        var pN = bi.parentNode;
                        if (pN.tagName == "DIV" && pN.childNodes.length == 1) {
                            pN.innerHTML = "<p><br/></p>";
                            domUtils.remove(pN);
                        }
                    }
                    var divs = div.querySelectorAll("#baidu_pastebin");
                    for (var i = 0, di; (di = divs[i++]); ) {
                        var tmpP = me.document.createElement("p");
                        di.parentNode.insertBefore(tmpP, di);
                        while (di.firstChild) {
                            tmpP.appendChild(di.firstChild);
                        }
                        domUtils.remove(di);
                    }

                    var metas = div.querySelectorAll("meta");
                    for (var i = 0, ci; (ci = metas[i++]); ) {
                        domUtils.remove(ci);
                    }

                    var brs = div.querySelectorAll("br");
                    for (i = 0; (ci = brs[i++]); ) {
                        if (/^apple-/i.test(ci.className)) {
                            domUtils.remove(ci);
                        }
                    }
                }
                if (browser.gecko) {
                    var dirtyNodes = div.querySelectorAll("[_moz_dirty]");
                    for (i = 0; (ci = dirtyNodes[i++]); ) {
                        ci.removeAttribute("_moz_dirty");
                    }
                }
                if (!browser.ie) {
                    var spans = div.querySelectorAll("span.Apple-style-span");
                    for (var i = 0, ci; (ci = spans[i++]); ) {
                        domUtils.remove(ci, true);
                    }
                }

                //ie下使用innerHTML会产生多余的\r\n字符，也会产生&nbsp;这里过滤掉
                html = div.innerHTML; //.replace(/>(?:(\s|&nbsp;)*?)</g,'><');

                //过滤word粘贴过来的冗余属性
                html = UM.filterWord(html);
                //取消了忽略空白的第二个参数，粘贴过来的有些是有空白的，会被套上相关的标签
                var root = UM.htmlparser(html);
                //如果给了过滤规则就先进行过滤
                if (me.options.filterRules) {
                    UM.filterNode(root, me.options.filterRules);
                }
                //执行默认的处理
                me.filterInputRule(root);
                //针对chrome的处理
                if (browser.webkit) {
                    var br = root.lastChild();
                    if (br && br.type == "element" && br.tagName == "br") {
                        root.removeChild(br);
                    }
                    utils.each(me.body.querySelectorAll("div"), function (node) {
                        if (domUtils.isEmptyBlock(node)) {
                            domUtils.remove(node);
                        }
                    });
                }
                html = { html: root.toHtml() };
                me.fireEvent("beforepaste", html, root);
                //抢了默认的粘贴，那后边的内容就不执行了，比如表格粘贴
                if (!html.html) {
                    return;
                }

                me.execCommand("insertHtml", html.html, true);
                me.fireEvent("afterpaste", html);
            }
        }

        me.addListener("ready", function () {
            $(me.body)
                .on("cut", function () {
                    var range = me.selection.getRange();
                    if (!range.collapsed && me.undoManger) {
                        me.undoManger.save();
                    }
                })
                .on(browser.ie || browser.opera ? "keydown" : "paste", function (e) {
                    //ie下beforepaste在点击右键时也会触发，所以用监控键盘才处理
                    if ((browser.ie || browser.opera) && ((!e.ctrlKey && !e.metaKey) || e.keyCode != "86")) {
                        return;
                    }
                    getClipboardData.call(me, function (div) {
                        filter(div);
                    });
                });
        });
    };

    ///import core
    ///commands 有序列表,无序列表
    ///commandsName  InsertOrderedList,InsertUnorderedList
    ///commandsTitle  有序列表,无序列表
    /**
     * 有序列表
     * @function
     * @name UM.execCommand
     * @param   {String}   cmdName     insertorderlist插入有序列表
     * @param   {String}   style               值为：decimal,lower-alpha,lower-roman,upper-alpha,upper-roman
     * @author zhanyi
     */
    /**
     * 无序链接
     * @function
     * @name UM.execCommand
     * @param   {String}   cmdName     insertunorderlist插入无序列表
     * * @param   {String}   style            值为：circle,disc,square
     * @author zhanyi
     */

    UM.plugins["list"] = function () {
        var me = this;

        me.setOpt({
            insertorderedlist: {
                decimal: "",
                "lower-alpha": "",
                "lower-roman": "",
                "upper-alpha": "",
                "upper-roman": "",
            },
            insertunorderedlist: {
                circle: "",
                disc: "",
                square: "",
            },
        });

        this.addInputRule(function (root) {
            utils.each(root.getNodesByTagName("li"), function (node) {
                if (node.children.length == 0) {
                    node.parentNode.removeChild(node);
                }
            });
        });
        me.commands["insertorderedlist"] = me.commands["insertunorderedlist"] = {
            execCommand: function (cmdName) {
                this.document.execCommand(cmdName);
                var rng = this.selection.getRange(),
                    bk = rng.createBookmark(true);

                this.$body.find("ol,ul").each(function (i, n) {
                    var parent = n.parentNode;
                    if (parent.tagName == "P" && parent.lastChild === parent.firstChild) {
                        $(n)
                            .children()
                            .each(function (j, li) {
                                var p = parent.cloneNode(false);
                                $(p).append(li.innerHTML);
                                $(li).html("").append(p);
                            });
                        $(n).insertBefore(parent);
                        $(parent).remove();
                    }

                    if (dtd.$inline[parent.tagName]) {
                        if (parent.tagName == "SPAN") {
                            $(n)
                                .children()
                                .each(function (k, li) {
                                    var span = parent.cloneNode(false);
                                    if (li.firstChild.nodeName != "P") {
                                        while (li.firstChild) {
                                            span.appendChild(li.firstChild);
                                        }
                                        $("<p></p>").appendTo(li).append(span);
                                    } else {
                                        while (li.firstChild) {
                                            span.appendChild(li.firstChild);
                                        }
                                        $(li.firstChild).append(span);
                                    }
                                });
                        }
                        domUtils.remove(parent, true);
                    }
                });

                rng.moveToBookmark(bk).select();
                return true;
            },
            queryCommandState: function (cmdName) {
                return this.document.queryCommandState(cmdName);
            },
        };
    };

    ///import core
    ///import plugins/serialize.js
    ///import plugins/undo.js
    ///commands 查看源码
    ///commandsName  Source
    ///commandsTitle  查看源码
    (function () {
        var sourceEditors = {
            textarea: function (editor, holder) {
                var textarea = holder.ownerDocument.createElement("textarea");
                textarea.style.cssText = "resize:none;border:0;padding:0;margin:0;overflow-y:auto;outline:0";
                // todo: IE下只有onresize属性可用... 很纠结
                if (browser.ie && browser.version < 8) {
                    textarea.style.width = holder.offsetWidth + "px";
                    textarea.style.height = holder.offsetHeight + "px";
                    holder.onresize = function () {
                        textarea.style.width = holder.offsetWidth + "px";
                        textarea.style.height = holder.offsetHeight + "px";
                    };
                }
                holder.appendChild(textarea);
                return {
                    container: textarea,
                    setContent: function (content) {
                        textarea.value = content;
                    },
                    getContent: function () {
                        return textarea.value;
                    },
                    select: function () {
                        var range;
                        if (browser.ie) {
                            range = textarea.createTextRange();
                            range.collapse(true);
                            range.select();
                        } else {
                            //todo: chrome下无法设置焦点
                            textarea.setSelectionRange(0, 0);
                            textarea.focus();
                        }
                    },
                    dispose: function () {
                        holder.removeChild(textarea);
                        // todo
                        holder.onresize = null;
                        textarea = null;
                        holder = null;
                    },
                };
            },
        };

        UM.plugins["source"] = function () {
            var me = this;
            var opt = this.options;
            var sourceMode = false;
            var sourceEditor;

            opt.sourceEditor = "textarea";

            me.setOpt({
                sourceEditorFirst: false,
            });
            function createSourceEditor(holder) {
                return sourceEditors.textarea(me, holder);
            }

            var bakCssText;
            //解决在源码模式下getContent不能得到最新的内容问题
            var oldGetContent = me.getContent,
                bakAddress;

            me.commands["source"] = {
                execCommand: function () {
                    sourceMode = !sourceMode;
                    if (sourceMode) {
                        bakAddress = me.selection.getRange().createAddress(false, true);
                        me.undoManger && me.undoManger.save(true);
                        if (browser.gecko) {
                            me.body.contentEditable = false;
                        }

                        //                    bakCssText = me.body.style.cssText;
                        me.body.style.cssText += ";position:absolute;left:-32768px;top:-32768px;";

                        me.fireEvent("beforegetcontent");
                        var root = UM.htmlparser(me.body.innerHTML);
                        me.filterOutputRule(root);
                        root.traversal(function (node) {
                            if (node.type == "element") {
                                switch (node.tagName) {
                                    case "td":
                                    case "th":
                                    case "caption":
                                        if (node.children && node.children.length == 1) {
                                            if (node.firstChild().tagName == "br") {
                                                node.removeChild(node.firstChild());
                                            }
                                        }
                                        break;
                                    case "pre":
                                        node.innerText(node.innerText().replace(/&nbsp;/g, " "));
                                }
                            }
                        });

                        me.fireEvent("aftergetcontent");

                        var content = root.toHtml(true);

                        sourceEditor = createSourceEditor(me.body.parentNode);

                        sourceEditor.setContent(content);

                        var getStyleValue = function (attr) {
                            return parseInt($(me.body).css(attr));
                        };
                        $(sourceEditor.container)
                            .width($(me.body).width() + getStyleValue("padding-left") + getStyleValue("padding-right"))
                            .height($(me.body).height());
                        setTimeout(function () {
                            sourceEditor.select();
                        });
                        //重置getContent，源码模式下取值也能是最新的数据
                        me.getContent = function () {
                            return sourceEditor.getContent() || "<p>" + (browser.ie ? "" : "<br/>") + "</p>";
                        };
                    } else {
                        me.$body.css({
                            position: "",
                            left: "",
                            top: "",
                        });
                        //                    me.body.style.cssText = bakCssText;
                        var cont = sourceEditor.getContent() || "<p>" + (browser.ie ? "" : "<br/>") + "</p>";
                        //处理掉block节点前后的空格,有可能会误命中，暂时不考虑
                        cont = cont.replace(new RegExp("[\\r\\t\\n ]*</?(\\w+)\\s*(?:[^>]*)>", "g"), function (a, b) {
                            if (b && !dtd.$inlineWithA[b.toLowerCase()]) {
                                return a.replace(/(^[\n\r\t ]*)|([\n\r\t ]*$)/g, "");
                            }
                            return a.replace(/(^[\n\r\t]*)|([\n\r\t]*$)/g, "");
                        });
                        me.setContent(cont);
                        sourceEditor.dispose();
                        sourceEditor = null;
                        //还原getContent方法
                        me.getContent = oldGetContent;
                        var first = me.body.firstChild;
                        //trace:1106 都删除空了，下边会报错，所以补充一个p占位
                        if (!first) {
                            me.body.innerHTML = "<p>" + (browser.ie ? "" : "<br/>") + "</p>";
                        }
                        //要在ifm为显示时ff才能取到selection,否则报错
                        //这里不能比较位置了
                        me.undoManger && me.undoManger.save(true);
                        if (browser.gecko) {
                            me.body.contentEditable = true;
                        }
                        try {
                            me.selection.getRange().moveToAddress(bakAddress).select();
                        } catch (e) {}
                    }
                    this.fireEvent("sourcemodechanged", sourceMode);
                },
                queryCommandState: function () {
                    return sourceMode | 0;
                },
                notNeedUndo: 1,
            };
            var oldQueryCommandState = me.queryCommandState;

            me.queryCommandState = function (cmdName) {
                cmdName = cmdName.toLowerCase();
                if (sourceMode) {
                    //源码模式下可以开启的命令
                    return cmdName in
                        {
                            source: 1,
                            fullscreen: 1,
                        }
                        ? oldQueryCommandState.apply(this, arguments)
                        : -1;
                }
                return oldQueryCommandState.apply(this, arguments);
            };
        };
    })();
    ///import core
    ///import plugins/undo.js
    ///commands 设置回车标签p或br
    ///commandsName  EnterKey
    ///commandsTitle  设置回车标签p或br
    /**
     * @description 处理回车
     * @author zhanyi
     */
    UM.plugins["enterkey"] = function () {
        var hTag,
            me = this,
            tag = me.options.enterTag;
        me.addListener("keyup", function (type, evt) {
            var keyCode = evt.keyCode || evt.which;
            if (keyCode == 13) {
                var range = me.selection.getRange(),
                    start = range.startContainer,
                    doSave;

                //修正在h1-h6里边回车后不能嵌套p的问题
                if (!browser.ie) {
                    if (/h\d/i.test(hTag)) {
                        if (browser.gecko) {
                            var h = domUtils.findParentByTagName(start, ["h1", "h2", "h3", "h4", "h5", "h6", "blockquote", "caption", "table"], true);
                            if (!h) {
                                me.document.execCommand("formatBlock", false, "<p>");
                                doSave = 1;
                            }
                        } else {
                            //chrome remove div
                            if (start.nodeType == 1) {
                                var tmp = me.document.createTextNode(""),
                                    div;
                                range.insertNode(tmp);
                                div = domUtils.findParentByTagName(tmp, "div", true);
                                if (div) {
                                    var p = me.document.createElement("p");
                                    while (div.firstChild) {
                                        p.appendChild(div.firstChild);
                                    }
                                    div.parentNode.insertBefore(p, div);
                                    domUtils.remove(div);
                                    range.setStartBefore(tmp).setCursor();
                                    doSave = 1;
                                }
                                domUtils.remove(tmp);
                            }
                        }

                        if (me.undoManger && doSave) {
                            me.undoManger.save();
                        }
                    }
                    //没有站位符，会出现多行的问题
                    browser.opera && range.select();
                } else {
                    me.fireEvent("saveScene", true, true);
                }
            }
        });

        me.addListener("keydown", function (type, evt) {
            var keyCode = evt.keyCode || evt.which;
            if (keyCode == 13) {
                //回车
                if (me.fireEvent("beforeenterkeydown")) {
                    domUtils.preventDefault(evt);
                    return;
                }
                me.fireEvent("saveScene", true, true);
                hTag = "";

                var range = me.selection.getRange();

                if (!range.collapsed) {
                    //跨td不能删
                    var start = range.startContainer,
                        end = range.endContainer,
                        startTd = domUtils.findParentByTagName(start, "td", true),
                        endTd = domUtils.findParentByTagName(end, "td", true);
                    if ((startTd && endTd && startTd !== endTd) || (!startTd && endTd) || (startTd && !endTd)) {
                        evt.preventDefault ? evt.preventDefault() : (evt.returnValue = false);
                        return;
                    }
                }
                if (tag == "p") {
                    if (!browser.ie) {
                        start = domUtils.findParentByTagName(range.startContainer, ["ol", "ul", "p", "h1", "h2", "h3", "h4", "h5", "h6", "blockquote", "caption"], true);

                        //opera下执行formatblock会在table的场景下有问题，回车在opera原生支持很好，所以暂时在opera去掉调用这个原生的command
                        //trace:2431
                        if (!start && !browser.opera) {
                            me.document.execCommand("formatBlock", false, "<p>");

                            if (browser.gecko) {
                                range = me.selection.getRange();
                                start = domUtils.findParentByTagName(range.startContainer, "p", true);
                                start && domUtils.removeDirtyAttr(start);
                            }
                        } else {
                            hTag = start.tagName;
                            start.tagName.toLowerCase() == "p" && browser.gecko && domUtils.removeDirtyAttr(start);
                        }
                    }
                }
            }
        });

        browser.ie &&
            me.addListener("setDisabled", function () {
                $(me.body)
                    .find("p")
                    .each(function (i, p) {
                        if (domUtils.isEmptyBlock(p)) {
                            p.innerHTML = "&nbsp;";
                        }
                    });
            });
    };

    ///import core
    ///commands 预览
    ///commandsName  Preview
    ///commandsTitle  预览
    /**
     * 预览
     * @function
     * @name UM.execCommand
     * @param   {String}   cmdName     preview预览编辑器内容
     */
    UM.commands["preview"] = {
        execCommand: function () {
            var w = window.open("", "_blank", ""),
                d = w.document,
                c = this.getContent(null, null, true),
                path = this.getOpt("UMEDITOR_HOME_URL"),
                formula =
                    c.indexOf("mathquill-embedded-latex") != -1
                        ? '<link rel="stylesheet" href="' +
                          path +
                          'third-party/mathquill/mathquill.css"/>' +
                          '<script src="' +
                          path +
                          'third-party/jquery.min.js"></script>' +
                          '<script src="' +
                          path +
                          'third-party/mathquill/mathquill.min.js"></script>'
                        : "";
            d.open();
            d.write("<html><head>" + formula + "</head><body><div>" + c + "</div></body></html>");
            d.close();
        },
        notNeedUndo: 1,
    };

    ///import core
    ///commands 加粗,斜体,上标,下标
    ///commandsName  Bold,Italic,Subscript,Superscript
    ///commandsTitle  加粗,加斜,下标,上标
    /**
     * b u i等基础功能实现
     * @function
     * @name UM.execCommands
     * @param    {String}    cmdName    bold加粗。italic斜体。subscript上标。superscript下标。
     */
    UM.plugins["basestyle"] = function () {
        var basestyles = ["bold", "underline", "superscript", "subscript", "italic", "strikethrough"],
            me = this;
        //添加快捷键
        me.addshortcutkey({
            Bold: "ctrl+66", //^B
            Italic: "ctrl+73", //^I
            Underline: "ctrl+shift+85", //^U
            strikeThrough: "ctrl+shift+83", //^s
        });
        //过滤最后的产出数据
        me.addOutputRule(function (root) {
            $.each(root.getNodesByTagName("b i u strike s"), function (i, node) {
                switch (node.tagName) {
                    case "b":
                        node.tagName = "strong";
                        break;
                    case "i":
                        node.tagName = "em";
                        break;
                    case "u":
                        node.tagName = "span";
                        node.setStyle("text-decoration", "underline");
                        break;
                    case "s":
                    case "strike":
                        node.tagName = "span";
                        node.setStyle("text-decoration", "line-through");
                }
            });
        });
        $.each(basestyles, function (i, cmd) {
            me.commands[cmd] = {
                execCommand: function (cmdName) {
                    var rng = this.selection.getRange();
                    if (rng.collapsed && this.queryCommandState(cmdName) != 1) {
                        var node = this.document.createElement(
                            {
                                bold: "strong",
                                underline: "u",
                                superscript: "sup",
                                subscript: "sub",
                                italic: "em",
                                strikethrough: "strike",
                            }[cmdName]
                        );
                        rng.insertNode(node).setStart(node, 0).setCursor(false);
                        return true;
                    } else {
                        return this.document.execCommand(cmdName);
                    }
                },
                queryCommandState: function (cmdName) {
                    if (browser.gecko) {
                        return this.document.queryCommandState(cmdName);
                    }
                    var path = this.selection.getStartElementPath(),
                        result = false;
                    $.each(path, function (i, n) {
                        switch (cmdName) {
                            case "bold":
                                if (n.nodeName == "STRONG" || n.nodeName == "B") {
                                    result = 1;
                                    return false;
                                }
                                break;
                            case "underline":
                                if (n.nodeName == "U" || (n.nodeName == "SPAN" && $(n).css("text-decoration") == "underline")) {
                                    result = 1;
                                    return false;
                                }
                                break;
                            case "superscript":
                                if (n.nodeName == "SUP") {
                                    result = 1;
                                    return false;
                                }
                                break;
                            case "subscript":
                                if (n.nodeName == "SUB") {
                                    result = 1;
                                    return false;
                                }
                                break;
                            case "italic":
                                if (n.nodeName == "EM" || n.nodeName == "I") {
                                    result = 1;
                                    return false;
                                }
                                break;
                            case "strikethrough":
                                if (n.nodeName == "S" || n.nodeName == "STRIKE" || (n.nodeName == "SPAN" && $(n).css("text-decoration") == "line-through")) {
                                    result = 1;
                                    return false;
                                }
                                break;
                        }
                    });
                    return result;
                },
            };
        });
    };

    ///import core
    ///import plugins/inserthtml.js
    ///commands 视频
    ///commandsName InsertVideo
    ///commandsTitle  插入视频
    ///commandsDialog  dialogs\video
    UM.plugins["video"] = function () {
        var me = this,
            div;

        /**
         * 创建插入视频字符窜
         * @param url 视频地址
         * @param width 视频宽度
         * @param height 视频高度
         * @param align 视频对齐
         * @param toEmbed 是否以flash代替显示
         * @param addParagraph  是否需要添加P 标签
         */
        function creatInsertStr(url, width, height, id, align, toEmbed) {
            return !toEmbed
                ? "<img " +
                      (id ? 'id="' + id + '"' : "") +
                      ' width="' +
                      width +
                      '" height="' +
                      height +
                      '" _url="' +
                      url +
                      '" class="edui-faked-video"' +
                      ' src="' +
                      me.options.UMEDITOR_HOME_URL +
                      'themes/default/images/spacer.gif" style="background:url(' +
                      me.options.UMEDITOR_HOME_URL +
                      "themes/default/images/videologo.gif) no-repeat center center; border:1px solid gray;" +
                      (align ? "float:" + align + ";" : "") +
                      '" />'
                : '<embed type="application/x-shockwave-flash" class="edui-faked-video" pluginspage="http://www.macromedia.com/go/getflashplayer"' +
                      ' src="' +
                      url +
                      '" width="' +
                      width +
                      '" height="' +
                      height +
                      '"' +
                      (align ? ' style="float:' + align + '"' : "") +
                      ' wmode="transparent" play="true" loop="false" menu="false" allowscriptaccess="never" allowfullscreen="true" >';
        }

        function switchImgAndEmbed(root, img2embed) {
            utils.each(root.getNodesByTagName(img2embed ? "img" : "embed"), function (node) {
                if (node.getAttr("class") == "edui-faked-video") {
                    var html = creatInsertStr(
                        img2embed ? node.getAttr("_url") : node.getAttr("src"),
                        node.getAttr("width"),
                        node.getAttr("height"),
                        null,
                        node.getStyle("float") || "",
                        img2embed
                    );
                    node.parentNode.replaceChild(UM.uNode.createElement(html), node);
                }
            });
        }

        me.addOutputRule(function (root) {
            switchImgAndEmbed(root, true);
        });
        me.addInputRule(function (root) {
            switchImgAndEmbed(root);
        });

        me.commands["insertvideo"] = {
            execCommand: function (cmd, videoObjs) {
                videoObjs = utils.isArray(videoObjs) ? videoObjs : [videoObjs];
                var html = [],
                    id = "tmpVedio";
                for (var i = 0, vi, len = videoObjs.length; i < len; i++) {
                    vi = videoObjs[i];
                    html.push(creatInsertStr(vi.url, vi.width || 420, vi.height || 280, id + i, vi.align, false));
                }
                me.execCommand("inserthtml", html.join(""), true);
            },
            queryCommandState: function () {
                var img = me.selection.getRange().getClosedNode(),
                    flag = img && img.className == "edui-faked-video";
                return flag ? 1 : 0;
            },
        };
    };
    ///import core
    ///commands 全选
    ///commandsName  SelectAll
    ///commandsTitle  全选
    /**
     * 选中所有
     * @function
     * @name UM.execCommand
     * @param   {String}   cmdName    selectall选中编辑器里的所有内容
     * @author zhanyi
     */
    UM.plugins["selectall"] = function () {
        var me = this;
        me.commands["selectall"] = {
            execCommand: function () {
                //去掉了原生的selectAll,因为会出现报错和当内容为空时，不能出现闭合状态的光标
                var me = this,
                    body = me.body,
                    range = me.selection.getRange();
                range.selectNodeContents(body);
                if (domUtils.isEmptyBlock(body)) {
                    //opera不能自动合并到元素的里边，要手动处理一下
                    if (browser.opera && body.firstChild && body.firstChild.nodeType == 1) {
                        range.setStartAtFirst(body.firstChild);
                    }
                    range.collapse(true);
                }
                range.select(true);
            },
            notNeedUndo: 1,
        };

        //快捷键
        me.addshortcutkey({
            selectAll: "ctrl+65",
        });
    };

    //UM.plugins['removeformat'] = function () {
    //    var me = this;
    //    me.commands['removeformat'] = {
    //        execCommand: function () {
    //            me.document.execCommand('removeformat');
    //
    //            /* 处理ie8和firefox选区有链接时,清除格式的bug */
    //            if (browser.gecko || browser.ie8 || browser.webkit) {
    //                var nativeRange = this.selection.getNative().getRangeAt(0),
    //                    common = nativeRange.commonAncestorContainer,
    //                    rng = me.selection.getRange(),
    //                    bk = rng.createBookmark();
    //
    //                function isEleInBookmark(node, bk){
    //                    if ( (domUtils.getPosition(node, bk.start) & domUtils.POSITION_FOLLOWING) &&
    //                        (domUtils.getPosition(bk.end, node) & domUtils.POSITION_FOLLOWING) ) {
    //                        return true;
    //                    } else if ( (domUtils.getPosition(node, bk.start) & domUtils.POSITION_CONTAINS) ||
    //                        (domUtils.getPosition(node, bk.end) & domUtils.POSITION_CONTAINS) ) {
    //                        return true;
    //                    }
    //                    return false;
    //                }
    //
    //                $(common).find('a').each(function (k, a) {
    //                    if ( isEleInBookmark(a, bk) ) {
    //                        a.removeAttribute('style');
    //                    }
    //                });
    //
    //            }
    //        }
    //    };
    //
    //};
    //

    UM.plugins["removeformat"] = function () {
        var me = this;
        me.setOpt({
            removeFormatTags: "b,big,code,del,dfn,em,font,i,ins,kbd,q,samp,small,span,strike,strong,sub,sup,tt,u,var",
            removeFormatAttributes: "class,style,lang,width,height,align,hspace,valign",
        });
        me.commands["removeformat"] = {
            execCommand: function (cmdName, tags, style, attrs, notIncludeA) {
                var tagReg = new RegExp("^(?:" + (tags || this.options.removeFormatTags).replace(/,/g, "|") + ")$", "i"),
                    removeFormatAttributes = style ? [] : (attrs || this.options.removeFormatAttributes).split(","),
                    range = new dom.Range(this.document),
                    bookmark,
                    node,
                    parent,
                    filter = function (node) {
                        return node.nodeType == 1;
                    };

                function isRedundantSpan(node) {
                    if (node.nodeType == 3 || node.tagName.toLowerCase() != "span") {
                        return 0;
                    }
                    if (browser.ie) {
                        //ie 下判断实效，所以只能简单用style来判断
                        //return node.style.cssText == '' ? 1 : 0;
                        var attrs = node.attributes;
                        if (attrs.length) {
                            for (var i = 0, l = attrs.length; i < l; i++) {
                                if (attrs[i].specified) {
                                    return 0;
                                }
                            }
                            return 1;
                        }
                    }
                    return !node.attributes.length;
                }
                function doRemove(range) {
                    var bookmark1 = range.createBookmark();
                    if (range.collapsed) {
                        range.enlarge(true);
                    }

                    //不能把a标签切了
                    if (!notIncludeA) {
                        var aNode = domUtils.findParentByTagName(range.startContainer, "a", true);
                        if (aNode) {
                            range.setStartBefore(aNode);
                        }

                        aNode = domUtils.findParentByTagName(range.endContainer, "a", true);
                        if (aNode) {
                            range.setEndAfter(aNode);
                        }
                    }

                    bookmark = range.createBookmark();

                    node = bookmark.start;

                    //切开始
                    while ((parent = node.parentNode) && !domUtils.isBlockElm(parent)) {
                        domUtils.breakParent(node, parent);
                        domUtils.clearEmptySibling(node);
                    }
                    if (bookmark.end) {
                        //切结束
                        node = bookmark.end;
                        while ((parent = node.parentNode) && !domUtils.isBlockElm(parent)) {
                            domUtils.breakParent(node, parent);
                            domUtils.clearEmptySibling(node);
                        }

                        //开始去除样式
                        var current = domUtils.getNextDomNode(bookmark.start, false, filter),
                            next;
                        while (current) {
                            if (current == bookmark.end) {
                                break;
                            }

                            next = domUtils.getNextDomNode(current, true, filter);

                            if (!dtd.$empty[current.tagName.toLowerCase()] && !domUtils.isBookmarkNode(current)) {
                                if (tagReg.test(current.tagName)) {
                                    if (style) {
                                        domUtils.removeStyle(current, style);
                                        if (isRedundantSpan(current) && style != "text-decoration") {
                                            domUtils.remove(current, true);
                                        }
                                    } else {
                                        domUtils.remove(current, true);
                                    }
                                } else {
                                    //trace:939  不能把list上的样式去掉
                                    if (!dtd.$tableContent[current.tagName] && !dtd.$list[current.tagName]) {
                                        domUtils.removeAttributes(current, removeFormatAttributes);
                                        if (isRedundantSpan(current)) {
                                            domUtils.remove(current, true);
                                        }
                                    }
                                }
                            }
                            current = next;
                        }
                    }
                    //trace:1035
                    //trace:1096 不能把td上的样式去掉，比如边框
                    var pN = bookmark.start.parentNode;
                    if (domUtils.isBlockElm(pN) && !dtd.$tableContent[pN.tagName] && !dtd.$list[pN.tagName]) {
                        domUtils.removeAttributes(pN, removeFormatAttributes);
                    }
                    pN = bookmark.end.parentNode;
                    if (bookmark.end && domUtils.isBlockElm(pN) && !dtd.$tableContent[pN.tagName] && !dtd.$list[pN.tagName]) {
                        domUtils.removeAttributes(pN, removeFormatAttributes);
                    }
                    range.moveToBookmark(bookmark).moveToBookmark(bookmark1);
                    //清除冗余的代码 <b><bookmark></b>
                    var node = range.startContainer,
                        tmp,
                        collapsed = range.collapsed;
                    while (node.nodeType == 1 && domUtils.isEmptyNode(node) && dtd.$removeEmpty[node.tagName]) {
                        tmp = node.parentNode;
                        range.setStartBefore(node);
                        //trace:937
                        //更新结束边界
                        if (range.startContainer === range.endContainer) {
                            range.endOffset--;
                        }
                        domUtils.remove(node);
                        node = tmp;
                    }

                    if (!collapsed) {
                        node = range.endContainer;
                        while (node.nodeType == 1 && domUtils.isEmptyNode(node) && dtd.$removeEmpty[node.tagName]) {
                            tmp = node.parentNode;
                            range.setEndBefore(node);
                            domUtils.remove(node);

                            node = tmp;
                        }
                    }
                }

                range = this.selection.getRange();
                if (!range.collapsed) {
                    doRemove(range);
                    range.select();
                }
            },
        };
    };
    /*
     *   处理特殊键的兼容性问题
     */
    UM.plugins["keystrokes"] = function () {
        var me = this;
        var collapsed = true;
        me.addListener("keydown", function (type, evt) {
            var keyCode = evt.keyCode || evt.which,
                rng = me.selection.getRange();

            //处理全选的情况
            if (
                !rng.collapsed &&
                !(evt.ctrlKey || evt.shiftKey || evt.altKey || evt.metaKey) &&
                ((keyCode >= 65 && keyCode <= 90) ||
                    (keyCode >= 48 && keyCode <= 57) ||
                    (keyCode >= 96 && keyCode <= 111) ||
                    {
                        13: 1,
                        8: 1,
                        46: 1,
                    }[keyCode])
            ) {
                var tmpNode = rng.startContainer;
                if (domUtils.isFillChar(tmpNode)) {
                    rng.setStartBefore(tmpNode);
                }
                tmpNode = rng.endContainer;
                if (domUtils.isFillChar(tmpNode)) {
                    rng.setEndAfter(tmpNode);
                }
                rng.txtToElmBoundary();
                //结束边界可能放到了br的前边，要把br包含进来
                // x[xxx]<br/>
                if (rng.endContainer && rng.endContainer.nodeType == 1) {
                    tmpNode = rng.endContainer.childNodes[rng.endOffset];
                    if (tmpNode && domUtils.isBr(tmpNode)) {
                        rng.setEndAfter(tmpNode);
                    }
                }
                if (rng.startOffset == 0) {
                    tmpNode = rng.startContainer;
                    if (domUtils.isBoundaryNode(tmpNode, "firstChild")) {
                        tmpNode = rng.endContainer;
                        if (rng.endOffset == (tmpNode.nodeType == 3 ? tmpNode.nodeValue.length : tmpNode.childNodes.length) && domUtils.isBoundaryNode(tmpNode, "lastChild")) {
                            me.fireEvent("saveScene");
                            me.body.innerHTML = "<p>" + (browser.ie ? "" : "<br/>") + "</p>";
                            rng.setStart(me.body.firstChild, 0).setCursor(false, true);
                            me._selectionChange();
                            return;
                        }
                    }
                }
            }

            //处理backspace
            if (keyCode == 8) {
                rng = me.selection.getRange();
                collapsed = rng.collapsed;
                if (me.fireEvent("delkeydown", evt)) {
                    return;
                }
                var start, end;
                //避免按两次删除才能生效的问题
                if (rng.collapsed && rng.inFillChar()) {
                    start = rng.startContainer;

                    if (domUtils.isFillChar(start)) {
                        rng.setStartBefore(start).shrinkBoundary(true).collapse(true);
                        domUtils.remove(start);
                    } else {
                        start.nodeValue = start.nodeValue.replace(new RegExp("^" + domUtils.fillChar), "");
                        rng.startOffset--;
                        rng.collapse(true).select(true);
                    }
                }
                //解决选中control元素不能删除的问题
                if ((start = rng.getClosedNode())) {
                    me.fireEvent("saveScene");
                    rng.setStartBefore(start);
                    domUtils.remove(start);
                    rng.setCursor();
                    me.fireEvent("saveScene");
                    domUtils.preventDefault(evt);
                    return;
                }
                //阻止在table上的删除
                if (!browser.ie) {
                    start = domUtils.findParentByTagName(rng.startContainer, "table", true);
                    end = domUtils.findParentByTagName(rng.endContainer, "table", true);
                    if ((start && !end) || (!start && end) || start !== end) {
                        evt.preventDefault();
                        return;
                    }
                }
                start = rng.startContainer;
                if (rng.collapsed && start.nodeType == 1) {
                    var currentNode = start.childNodes[rng.startOffset - 1];
                    if (currentNode && currentNode.nodeType == 1 && currentNode.tagName == "BR") {
                        me.fireEvent("saveScene");
                        rng.setStartBefore(currentNode).collapse(true);
                        domUtils.remove(currentNode);
                        rng.select();
                        me.fireEvent("saveScene");
                    }
                }

                //trace:3613
                if (browser.chrome) {
                    if (rng.collapsed) {
                        while (rng.startOffset == 0 && !domUtils.isEmptyBlock(rng.startContainer)) {
                            rng.setStartBefore(rng.startContainer);
                        }
                        var pre = rng.startContainer.childNodes[rng.startOffset - 1];
                        if (pre && pre.nodeName == "BR") {
                            rng.setStartBefore(pre);
                            me.fireEvent("saveScene");
                            $(pre).remove();
                            rng.setCursor();
                            me.fireEvent("saveScene");
                        }
                    }
                }
            }
            //trace:1634
            //ff的del键在容器空的时候，也会删除
            if (browser.gecko && keyCode == 46) {
                var range = me.selection.getRange();
                if (range.collapsed) {
                    start = range.startContainer;
                    if (domUtils.isEmptyBlock(start)) {
                        var parent = start.parentNode;
                        while (domUtils.getChildCount(parent) == 1 && !domUtils.isBody(parent)) {
                            start = parent;
                            parent = parent.parentNode;
                        }
                        if (start === parent.lastChild) evt.preventDefault();
                        return;
                    }
                }
            }
        });
        me.addListener("keyup", function (type, evt) {
            var keyCode = evt.keyCode || evt.which,
                rng,
                me = this;
            if (keyCode == 8) {
                if (me.fireEvent("delkeyup")) {
                    return;
                }
                rng = me.selection.getRange();
                if (rng.collapsed) {
                    var tmpNode,
                        autoClearTagName = ["h1", "h2", "h3", "h4", "h5", "h6"];
                    if ((tmpNode = domUtils.findParentByTagName(rng.startContainer, autoClearTagName, true))) {
                        if (domUtils.isEmptyBlock(tmpNode)) {
                            var pre = tmpNode.previousSibling;
                            if (pre && pre.nodeName != "TABLE") {
                                domUtils.remove(tmpNode);
                                rng.setStartAtLast(pre).setCursor(false, true);
                                return;
                            } else {
                                var next = tmpNode.nextSibling;
                                if (next && next.nodeName != "TABLE") {
                                    domUtils.remove(tmpNode);
                                    rng.setStartAtFirst(next).setCursor(false, true);
                                    return;
                                }
                            }
                        }
                    }
                    //处理当删除到body时，要重新给p标签展位
                    if (domUtils.isBody(rng.startContainer)) {
                        var tmpNode = domUtils.createElement(me.document, "p", {
                            innerHTML: browser.ie ? domUtils.fillChar : "<br/>",
                        });
                        rng.insertNode(tmpNode).setStart(tmpNode, 0).setCursor(false, true);
                    }
                }

                //chrome下如果删除了inline标签，浏览器会有记忆，在输入文字还是会套上刚才删除的标签，所以这里再选一次就不会了
                if (!collapsed && (rng.startContainer.nodeType == 3 || (rng.startContainer.nodeType == 1 && domUtils.isEmptyBlock(rng.startContainer)))) {
                    if (browser.ie) {
                        var span = rng.document.createElement("span");
                        rng.insertNode(span).setStartBefore(span).collapse(true);
                        rng.select();
                        domUtils.remove(span);
                    } else {
                        rng.select();
                    }
                }
            }
        });
    };
    /**
     * 自动保存草稿
     */
    UM.plugins["autosave"] = function () {
        var me = this,
            //无限循环保护
            lastSaveTime = new Date(),
            //最小保存间隔时间
            MIN_TIME = 20,
            //auto save key
            saveKey = null;

        //默认间隔时间
        me.setOpt("saveInterval", 500);

        //存储媒介封装
        var LocalStorage = (UM.LocalStorage = (function () {
            var storage = window.localStorage || getUserData() || null,
                LOCAL_FILE = "localStorage";

            return {
                saveLocalData: function (key, data) {
                    if (storage && data) {
                        storage.setItem(key, data);
                        return true;
                    }

                    return false;
                },

                getLocalData: function (key) {
                    if (storage) {
                        return storage.getItem(key);
                    }

                    return null;
                },

                removeItem: function (key) {
                    storage && storage.removeItem(key);
                },
            };

            function getUserData() {
                var container = document.createElement("div");
                container.style.display = "none";

                if (!container.addBehavior) {
                    return null;
                }

                container.addBehavior("#default#userdata");

                return {
                    getItem: function (key) {
                        var result = null;

                        try {
                            document.body.appendChild(container);
                            container.load(LOCAL_FILE);
                            result = container.getAttribute(key);
                            document.body.removeChild(container);
                        } catch (e) {}

                        return result;
                    },

                    setItem: function (key, value) {
                        document.body.appendChild(container);
                        container.setAttribute(key, value);
                        container.save(LOCAL_FILE);
                        document.body.removeChild(container);
                    },
                    //               暂时没有用到
                    //                clear: function () {
                    //
                    //                    var expiresTime = new Date();
                    //                    expiresTime.setFullYear( expiresTime.getFullYear() - 1 );
                    //                    document.body.appendChild( container );
                    //                    container.expires = expiresTime.toUTCString();
                    //                    container.save( LOCAL_FILE );
                    //                    document.body.removeChild( container );
                    //
                    //                },

                    removeItem: function (key) {
                        document.body.appendChild(container);
                        container.removeAttribute(key);
                        container.save(LOCAL_FILE);
                        document.body.removeChild(container);
                    },
                };
            }
        })());

        function save(editor) {
            var saveData = null;

            if (new Date() - lastSaveTime < MIN_TIME) {
                return;
            }

            if (!editor.hasContents()) {
                //这里不能调用命令来删除， 会造成事件死循环
                saveKey && LocalStorage.removeItem(saveKey);
                return;
            }

            lastSaveTime = new Date();

            editor._saveFlag = null;

            saveData = me.body.innerHTML;

            if (
                editor.fireEvent("beforeautosave", {
                    content: saveData,
                }) === false
            ) {
                return;
            }

            LocalStorage.saveLocalData(saveKey, saveData);

            editor.fireEvent("afterautosave", {
                content: saveData,
            });
        }

        me.addListener("ready", function () {
            var _suffix = "-drafts-data",
                key = null;

            if (me.key) {
                key = me.key + _suffix;
            } else {
                key = (me.container.parentNode.id || "ue-common") + _suffix;
            }

            //页面地址+编辑器ID 保持唯一
            saveKey = (location.protocol + location.host + location.pathname).replace(/[.:\/]/g, "_") + key;
        });

        me.addListener("contentchange", function () {
            if (!saveKey) {
                return;
            }

            if (me._saveFlag) {
                window.clearTimeout(me._saveFlag);
            }

            if (me.options.saveInterval > 0) {
                me._saveFlag = window.setTimeout(function () {
                    save(me);
                }, me.options.saveInterval);
            } else {
                save(me);
            }
        });

        me.commands["clearlocaldata"] = {
            execCommand: function (cmd, name) {
                if (saveKey && LocalStorage.getLocalData(saveKey)) {
                    LocalStorage.removeItem(saveKey);
                }
            },
            notNeedUndo: true,
            ignoreContentChange: true,
        };

        me.commands["getlocaldata"] = {
            execCommand: function (cmd, name) {
                return saveKey ? LocalStorage.getLocalData(saveKey) || "" : "";
            },
            notNeedUndo: true,
            ignoreContentChange: true,
        };

        me.commands["drafts"] = {
            execCommand: function (cmd, name) {
                if (saveKey) {
                    me.body.innerHTML = LocalStorage.getLocalData(saveKey) || "<p>" + (browser.ie ? "&nbsp;" : "<br/>") + "</p>";
                    me.focus(true);
                }
            },
            queryCommandState: function () {
                return saveKey ? (LocalStorage.getLocalData(saveKey) === null ? -1 : 0) : -1;
            },
            notNeedUndo: true,
            ignoreContentChange: true,
        };
    };

    /**
     * @description
     * 1.拖放文件到编辑区域，自动上传并插入到选区
     * 2.插入粘贴板的图片，自动上传并插入到选区
     * @author Jinqn
     * @date 2013-10-14
     */
    UM.plugins["autoupload"] = function () {
        var me = this;

        me.setOpt("pasteImageEnabled", true);
        me.setOpt("dropFileEnabled", true);
        var sendAndInsertImage = function (file, editor) {
            //模拟数据
            var fd = new FormData();
            fd.append(editor.options.imageFieldName || "upfile", file, file.name || "blob." + file.type.substr("image/".length));
            fd.append("type", "ajax");
            var xhr = new XMLHttpRequest();
            xhr.open("post", me.options.imageUrl, true);
            xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
            xhr.addEventListener("load", function (e) {
                try {
                    var json = eval("(" + e.target.response + ")"),
                        link = json.url,
                        picLink = me.options.imagePath + link;
                    editor.execCommand("insertimage", {
                        src: picLink,
                        _src: picLink,
                    });
                } catch (er) {}
            });
            xhr.send(fd);
        };

        function getPasteImage(e) {
            return e.clipboardData && e.clipboardData.items && e.clipboardData.items.length == 1 && /^image\//.test(e.clipboardData.items[0].type) ? e.clipboardData.items : null;
        }

        function getDropImage(e) {
            return e.dataTransfer && e.dataTransfer.files ? e.dataTransfer.files : null;
        }

        me.addListener("ready", function () {
            if (window.FormData && window.FileReader) {
                var autoUploadHandler = function (e) {
                    var hasImg = false,
                        items;
                    //获取粘贴板文件列表或者拖放文件列表
                    items = e.type == "paste" ? getPasteImage(e.originalEvent) : getDropImage(e.originalEvent);
                    if (items) {
                        var len = items.length,
                            file;
                        while (len--) {
                            file = items[len];
                            if (file.getAsFile) file = file.getAsFile();
                            if (file && file.size > 0 && /image\/\w+/i.test(file.type)) {
                                sendAndInsertImage(file, me);
                                hasImg = true;
                            }
                        }
                        if (hasImg) return false;
                    }
                };
                me.getOpt("pasteImageEnabled") && me.$body.on("paste", autoUploadHandler);
                me.getOpt("dropFileEnabled") && me.$body.on("drop", autoUploadHandler);

                //取消拖放图片时出现的文字光标位置提示
                me.$body.on("dragover", function (e) {
                    if (e.originalEvent.dataTransfer.types[0] == "Files") {
                        return false;
                    }
                });
            }
        });
    };
    /**
     * 公式插件
     */
    UM.plugins["formula"] = function () {
        var me = this;

        function getActiveIframe() {
            return me.$body.find("iframe.edui-formula-active")[0] || null;
        }

        function blurActiveIframe() {
            var iframe = getActiveIframe();
            iframe && iframe.contentWindow.formula.blur();
        }

        me.addInputRule(function (root) {
            $.each(root.getNodesByTagName("span"), function (i, node) {
                if (node.hasClass("mathquill-embedded-latex")) {
                    var firstChild,
                        latex = "";
                    while ((firstChild = node.firstChild())) {
                        latex += firstChild.data;
                        node.removeChild(firstChild);
                    }
                    node.tagName = "iframe";
                    node.setAttr({
                        frameborder: "0",
                        src: me.getOpt("UMEDITOR_HOME_URL") + "dialogs/formula/formula.html",
                        "data-latex": utils.unhtml(latex),
                    });
                }
            });
        });
        me.addOutputRule(function (root) {
            $.each(root.getNodesByTagName("iframe"), function (i, node) {
                if (node.hasClass("mathquill-embedded-latex")) {
                    node.tagName = "span";
                    node.appendChild(UM.uNode.createText(node.getAttr("data-latex")));
                    node.setAttr({
                        frameborder: "",
                        src: "",
                        "data-latex": "",
                    });
                }
            });
        });
        me.addListener("click", function () {
            blurActiveIframe();
        });
        me.addListener("afterexeccommand", function (type, cmd) {
            if (cmd != "formula") {
                blurActiveIframe();
            }
        });

        me.commands["formula"] = {
            execCommand: function (cmd, latex) {
                var iframe = getActiveIframe();
                if (iframe) {
                    iframe.contentWindow.formula.insertLatex(latex);
                } else {
                    me.execCommand("inserthtml", '<span class="mathquill-embedded-latex">' + latex + "</span>");
                    browser.ie &&
                        browser.ie9below &&
                        setTimeout(function () {
                            var rng = me.selection.getRange(),
                                startContainer = rng.startContainer;
                            if (startContainer.nodeType == 1 && !startContainer.childNodes[rng.startOffset]) {
                                rng.insertNode(me.document.createTextNode(" "));
                                rng.setCursor();
                            }
                        }, 100);
                }
            },
            queryCommandState: function (cmd) {
                return 0;
            },
            queryCommandValue: function (cmd) {
                var iframe = getActiveIframe();
                return iframe && iframe.contentWindow.formula.getLatex();
            },
        };
    };

    (function ($) {
        //对jquery的扩展
        $.parseTmpl = function parse(str, data) {
            var tmpl =
                "var __p=[],print=function(){__p.push.apply(__p,arguments);};" +
                "with(obj||{}){__p.push('" +
                str
                    .replace(/\\/g, "\\\\")
                    .replace(/'/g, "\\'")
                    .replace(/<%=([\s\S]+?)%>/g, function (match, code) {
                        return "'," + code.replace(/\\'/g, "'") + ",'";
                    })
                    .replace(/<%([\s\S]+?)%>/g, function (match, code) {
                        return "');" + code.replace(/\\'/g, "'").replace(/[\r\n\t]/g, " ") + "__p.push('";
                    })
                    .replace(/\r/g, "\\r")
                    .replace(/\n/g, "\\n")
                    .replace(/\t/g, "\\t") +
                "');}return __p.join('');";
            var func = new Function("obj", tmpl);
            return data ? func(data) : func;
        };
        $.extend2 = function (t, s) {
            var a = arguments,
                notCover = $.type(a[a.length - 1]) == "boolean" ? a[a.length - 1] : false,
                len = $.type(a[a.length - 1]) == "boolean" ? a.length - 1 : a.length;
            for (var i = 1; i < len; i++) {
                var x = a[i];
                for (var k in x) {
                    if (!notCover || !t.hasOwnProperty(k)) {
                        t[k] = x[k];
                    }
                }
            }
            return t;
        };

        $.IE6 = !!window.ActiveXObject && parseFloat(navigator.userAgent.match(/msie (\d+)/i)[1]) == 6;

        //所有ui的基类
        var _eventHandler = [];
        var _widget = function () {};
        var _prefix = "edui";
        _widget.prototype = {
            on: function (ev, cb) {
                this.root().on(ev, $.proxy(cb, this));
                return this;
            },
            off: function (ev, cb) {
                this.root().off(ev, $.proxy(cb, this));
                return this;
            },
            trigger: function (ev, data) {
                return this.root().trigger(ev, data) === false ? false : this;
            },
            root: function ($el) {
                return this._$el || (this._$el = $el);
            },
            destroy: function () {},
            data: function (key, val) {
                if (val !== undefined) {
                    this.root().data(_prefix + key, val);
                    return this;
                } else {
                    return this.root().data(_prefix + key);
                }
            },
            register: function (eventName, $el, fn) {
                _eventHandler.push({
                    evtname: eventName,
                    $els: $.isArray($el) ? $el : [$el],
                    handler: $.proxy(fn, $el),
                });
            },
        };

        //从jq实例上拿到绑定的widget实例
        $.fn.edui = function (obj) {
            return obj ? this.data("eduiwidget", obj) : this.data("eduiwidget");
        };

        function _createClass(ClassObj, properties, supperClass) {
            ClassObj.prototype = $.extend2($.extend({}, properties), (UM.ui[supperClass] || _widget).prototype, true);
            ClassObj.prototype.supper = (UM.ui[supperClass] || _widget).prototype;
            //父class的defaultOpt 合并
            if (UM.ui[supperClass] && UM.ui[supperClass].prototype.defaultOpt) {
                var parentDefaultOptions = UM.ui[supperClass].prototype.defaultOpt,
                    subDefaultOptions = ClassObj.prototype.defaultOpt;

                ClassObj.prototype.defaultOpt = $.extend({}, parentDefaultOptions, subDefaultOptions || {});
            }
            return ClassObj;
        }

        var _guid = 1;

        function mergeToJQ(ClassObj, className) {
            $[_prefix + className] = ClassObj;
            $.fn[_prefix + className] = function (opt) {
                var result,
                    args = Array.prototype.slice.call(arguments, 1);

                this.each(function (i, el) {
                    var $this = $(el);
                    var obj = $this.edui();
                    if (!obj) {
                        ClassObj(!opt || !$.isPlainObject(opt) ? {} : opt, $this);
                        $this.edui(obj);
                    }
                    if ($.type(opt) == "string") {
                        if (opt == "this") {
                            result = obj;
                        } else {
                            result = obj[opt].apply(obj, args);
                            if (result !== obj && result !== undefined) {
                                return false;
                            }
                            result = null;
                        }
                    }
                });

                return result !== null ? result : this;
            };
        }

        UM.ui = {
            define: function (className, properties, supperClass) {
                var ClassObj = (UM.ui[className] = _createClass(
                    function (options, $el) {
                        var _obj = function () {};
                        $.extend(_obj.prototype, ClassObj.prototype, {
                            guid: className + _guid++,
                            widgetName: className,
                        });
                        var obj = new _obj();
                        if ($.type(options) == "string") {
                            obj.init && obj.init({});
                            obj.root().edui(obj);
                            obj.root()
                                .find("a")
                                .click(function (evt) {
                                    evt.preventDefault();
                                });
                            return obj.root()[_prefix + className].apply(obj.root(), arguments);
                        } else {
                            $el && obj.root($el);
                            obj.init && obj.init(!options || $.isPlainObject(options) ? $.extend2(options || {}, obj.defaultOpt || {}, true) : options);
                            try {
                                obj.root()
                                    .find("a")
                                    .click(function (evt) {
                                        evt.preventDefault();
                                    });
                            } catch (e) {}

                            return obj.root().edui(obj);
                        }
                    },
                    properties,
                    supperClass
                ));

                mergeToJQ(ClassObj, className);
            },
        };

        $(function () {
            $(document).on("click mouseup mousedown dblclick mouseover", function (evt) {
                $.each(_eventHandler, function (i, obj) {
                    if (obj.evtname == evt.type) {
                        $.each(obj.$els, function (i, $el) {
                            if ($el[0] !== evt.target && !$.contains($el[0], evt.target)) {
                                obj.handler(evt);
                            }
                        });
                    }
                });
            });
        });
    })(jQuery);
    //button 类
    UM.ui.define("button", {
        tpl:
            '<<%if(!texttype){%>div class="edui-btn edui-btn-<%=icon%> <%if(name){%>edui-btn-name-<%=name%><%}%>" unselectable="on" onmousedown="return false" <%}else{%>a class="edui-text-btn"<%}%><% if(title) {%> data-original-title="<%=title%>" <%};%>> ' +
            '<% if(icon) {%><div unselectable="on" class="edui-icon-<%=icon%> edui-icon"></div><% }; %><%if(text) {%><span unselectable="on" onmousedown="return false" class="edui-button-label"><%=text%></span><%}%>' +
            '<%if(caret && text){%><span class="edui-button-spacing"></span><%}%>' +
            '<% if(caret) {%><span unselectable="on" onmousedown="return false" class="edui-caret"></span><% };%></<%if(!texttype){%>div<%}else{%>a<%}%>>',
        defaultOpt: {
            text: "",
            title: "",
            icon: "",
            width: "",
            caret: false,
            texttype: false,
            click: function () {},
        },
        init: function (options) {
            var me = this;

            me.root($($.parseTmpl(me.tpl, options))).click(function (evt) {
                me.wrapclick(options.click, evt);
            });

            me.root().hover(function () {
                if (!me.root().hasClass("edui-disabled")) {
                    me.root().toggleClass("edui-hover");
                }
            });

            return me;
        },
        wrapclick: function (fn, evt) {
            if (!this.disabled()) {
                this.root().trigger("wrapclick");
                $.proxy(fn, this, evt)();
            }
            return this;
        },
        label: function (text) {
            if (text === undefined) {
                return this.root().find(".edui-button-label").text();
            } else {
                this.root().find(".edui-button-label").text(text);
                return this;
            }
        },
        disabled: function (state) {
            if (state === undefined) {
                return this.root().hasClass("edui-disabled");
            }
            this.root().toggleClass("edui-disabled", state);
            if (this.root().hasClass("edui-disabled")) {
                this.root().removeClass("edui-hover");
            }
            return this;
        },
        active: function (state) {
            if (state === undefined) {
                return this.root().hasClass("edui-active");
            }
            this.root().toggleClass("edui-active", state);

            return this;
        },
        mergeWith: function ($obj) {
            var me = this;
            me.data("$mergeObj", $obj);
            $obj.edui().data("$mergeObj", me.root());
            if (!$.contains(document.body, $obj[0])) {
                $obj.appendTo(me.root());
            }
            me.on("click", function () {
                me.wrapclick(function () {
                    $obj.edui().show();
                });
            }).register("click", me.root(), function (evt) {
                $obj.hide();
            });
        },
    });
    //toolbar 类
    (function () {
        UM.ui.define("toolbar", {
            tpl: '<div class="edui-toolbar"  ><div class="edui-btn-toolbar" unselectable="on" onmousedown="return false"  ></div></div>',
            init: function () {
                var $root = this.root($(this.tpl));
                this.data("$btnToolbar", $root.find(".edui-btn-toolbar"));
            },
            appendToBtnmenu: function (data) {
                var $cont = this.data("$btnToolbar");
                data = $.isArray(data) ? data : [data];
                $.each(data, function (i, $item) {
                    $cont.append($item);
                });
            },
        });
    })();

    //menu 类
    UM.ui.define("menu", {
        show: function ($obj, dir, fnname, topOffset, leftOffset) {
            fnname = fnname || "position";
            if (this.trigger("beforeshow") === false) {
                return;
            } else {
                this.root().css(
                    $.extend(
                        { display: "block" },
                        $obj
                            ? {
                                  top: $obj[fnname]().top + (dir == "right" ? 0 : $obj.outerHeight()) - (topOffset || 0),
                                  left: $obj[fnname]().left + (dir == "right" ? $obj.outerWidth() : 0) - (leftOffset || 0),
                              }
                            : {}
                    )
                );
                this.trigger("aftershow");
            }
        },
        hide: function (all) {
            var $parentmenu;
            if (this.trigger("beforehide") === false) {
                return;
            } else {
                if (($parentmenu = this.root().data("parentmenu"))) {
                    if ($parentmenu.data("parentmenu") || all) $parentmenu.edui().hide();
                }
                this.root().css("display", "none");
                this.trigger("afterhide");
            }
        },
        attachTo: function ($obj) {
            var me = this;
            if (!$obj.data("$mergeObj")) {
                $obj.data("$mergeObj", me.root());
                $obj.on("wrapclick", function (evt) {
                    me.show();
                });
                me.register("click", $obj, function (evt) {
                    me.hide();
                });
                me.data("$mergeObj", $obj);
            }
        },
    });
    //dropmenu 类
    UM.ui.define(
        "dropmenu",
        {
            tmpl:
                '<ul class="edui-dropdown-menu" aria-labelledby="dropdownMenu" >' +
                "<%for(var i=0,ci;ci=data[i++];){%>" +
                '<%if(ci.divider){%><li class="edui-divider"></li><%}else{%>' +
                "<li <%if(ci.active||ci.disabled){%>class=\"<%= ci.active|| '' %> <%=ci.disabled||'' %>\" <%}%> data-value=\"<%= ci.value%>\">" +
                '<a href="#" tabindex="-1"><em class="edui-dropmenu-checkbox"><i class="edui-icon-ok"></i></em><%= ci.label%></a>' +
                "</li><%}%>" +
                "<%}%>" +
                "</ul>",
            defaultOpt: {
                data: [],
                click: function () {},
            },
            init: function (options) {
                var me = this;
                var eventName = {
                    click: 1,
                    mouseover: 1,
                    mouseout: 1,
                };

                this.root($($.parseTmpl(this.tmpl, options)))
                    .on("click", 'li[class!="edui-disabled edui-divider edui-dropdown-submenu"]', function (evt) {
                        $.proxy(options.click, me, evt, $(this).data("value"), $(this))();
                    })
                    .find("li")
                    .each(function (i, el) {
                        var $this = $(this);
                        if (!$this.hasClass("edui-disabled edui-divider edui-dropdown-submenu")) {
                            var data = options.data[i];
                            $.each(eventName, function (k) {
                                data[k] &&
                                    $this[k](function (evt) {
                                        $.proxy(data[k], el)(evt, data, me.root);
                                    });
                            });
                        }
                    });
            },
            disabled: function (cb) {
                $("li[class!=edui-divider]", this.root()).each(function () {
                    var $el = $(this);
                    if (cb === true) {
                        $el.addClass("edui-disabled");
                    } else if ($.isFunction(cb)) {
                        $el.toggleClass("edui-disabled", cb(li));
                    } else {
                        $el.removeClass("edui-disabled");
                    }
                });
            },
            val: function (val) {
                var currentVal;
                $('li[class!="edui-divider edui-disabled edui-dropdown-submenu"]', this.root()).each(function () {
                    var $el = $(this);
                    if (val === undefined) {
                        if ($el.find("em.edui-dropmenu-checked").length) {
                            currentVal = $el.data("value");
                            return false;
                        }
                    } else {
                        $el.find("em").toggleClass("edui-dropmenu-checked", $el.data("value") == val);
                    }
                });
                if (val === undefined) {
                    return currentVal;
                }
            },
            addSubmenu: function (label, menu, index) {
                index = index || 0;

                var $list = $("li[class!=edui-divider]", this.root());
                var $node = $('<li class="edui-dropdown-submenu"><a tabindex="-1" href="#">' + label + "</a></li>").append(menu);

                if (index >= 0 && index < $list.length) {
                    $node.insertBefore($list[index]);
                } else if (index < 0) {
                    $node.insertBefore($list[0]);
                } else if (index >= $list.length) {
                    $node.appendTo($list);
                }
            },
        },
        "menu"
    );
    //splitbutton 类
    ///import button
    UM.ui.define("splitbutton", {
        tpl:
            '<div class="edui-splitbutton <%if (name){%>edui-splitbutton-<%= name %><%}%>"  unselectable="on" <%if(title){%>data-original-title="<%=title%>"<%}%>><div class="edui-btn"  unselectable="on" ><%if(icon){%><div  unselectable="on" class="edui-icon-<%=icon%> edui-icon"></div><%}%><%if(text){%><%=text%><%}%></div>' +
            '<div  unselectable="on" class="edui-btn edui-dropdown-toggle" >' +
            '<div  unselectable="on" class="edui-caret"></div>' +
            "</div>" +
            "</div>",
        defaultOpt: {
            text: "",
            title: "",
            click: function () {},
        },
        init: function (options) {
            var me = this;
            me.root($($.parseTmpl(me.tpl, options)));
            me.root()
                .find(".edui-btn:first")
                .click(function (evt) {
                    if (!me.disabled()) {
                        $.proxy(options.click, me)();
                    }
                });
            me.root()
                .find(".edui-dropdown-toggle")
                .click(function () {
                    if (!me.disabled()) {
                        me.trigger("arrowclick");
                    }
                });
            me.root().hover(function () {
                if (!me.root().hasClass("edui-disabled")) {
                    me.root().toggleClass("edui-hover");
                }
            });

            return me;
        },
        wrapclick: function (fn, evt) {
            if (!this.disabled()) {
                $.proxy(fn, this, evt)();
            }
            return this;
        },
        disabled: function (state) {
            if (state === undefined) {
                return this.root().hasClass("edui-disabled");
            }
            this.root().toggleClass("edui-disabled", state).find(".edui-btn").toggleClass("edui-disabled", state);
            return this;
        },
        active: function (state) {
            if (state === undefined) {
                return this.root().hasClass("edui-active");
            }
            this.root().toggleClass("edui-active", state).find(".edui-btn:first").toggleClass("edui-active", state);
            return this;
        },
        mergeWith: function ($obj) {
            var me = this;
            me.data("$mergeObj", $obj);
            $obj.edui().data("$mergeObj", me.root());
            if (!$.contains(document.body, $obj[0])) {
                $obj.appendTo(me.root());
            }
            me.root().delegate(".edui-dropdown-toggle", "click", function () {
                me.wrapclick(function () {
                    $obj.edui().show();
                });
            });
            me.register("click", me.root().find(".edui-dropdown-toggle"), function (evt) {
                $obj.hide();
            });
        },
    });
    /**
     * Created with JetBrains PhpStorm.
     * User: hn
     * Date: 13-7-10
     * Time: 下午3:07
     * To change this template use File | Settings | File Templates.
     */
    UM.ui.define(
        "colorsplitbutton",
        {
            tpl:
                '<div class="edui-splitbutton <%if (name){%>edui-splitbutton-<%= name %><%}%>"  unselectable="on" <%if(title){%>data-original-title="<%=title%>"<%}%>><div class="edui-btn"  unselectable="on" ><%if(icon){%><div  unselectable="on" class="edui-icon-<%=icon%> edui-icon"></div><%}%><div class="edui-splitbutton-color-label" <%if (color) {%>style="background: <%=color%>"<%}%>></div><%if(text){%><%=text%><%}%></div>' +
                '<div  unselectable="on" class="edui-btn edui-dropdown-toggle" >' +
                '<div  unselectable="on" class="edui-caret"></div>' +
                "</div>" +
                "</div>",
            defaultOpt: {
                color: "",
            },
            init: function (options) {
                var me = this;

                me.supper.init.call(me, options);
            },
            colorLabel: function () {
                return this.root().find(".edui-splitbutton-color-label");
            },
        },
        "splitbutton"
    );
    //popup 类
    UM.ui.define("popup", {
        tpl:
            '<div class="edui-dropdown-menu edui-popup"' +
            '<%if(!<%=stopprop%>){%>onmousedown="return false"<%}%>' +
            '><div class="edui-popup-body" unselectable="on" onmousedown="return false"><%=subtpl%></div>' +
            '<div class="edui-popup-caret"></div>' +
            "</div>",
        defaultOpt: {
            stopprop: false,
            subtpl: "",
            width: "",
            height: "",
        },
        init: function (options) {
            this.root($($.parseTmpl(this.tpl, options)));
            return this;
        },
        mergeTpl: function (data) {
            return $.parseTmpl(this.tpl, { subtpl: data });
        },
        show: function ($obj, posObj) {
            if (!posObj) posObj = {};

            var fnname = posObj.fnname || "position";
            if (this.trigger("beforeshow") === false) {
                return;
            } else {
                this.root().css(
                    $.extend(
                        { display: "block" },
                        $obj
                            ? {
                                  top: $obj[fnname]().top + (posObj.dir == "right" ? 0 : $obj.outerHeight()) - (posObj.offsetTop || 0),
                                  left: $obj[fnname]().left + (posObj.dir == "right" ? $obj.outerWidth() : 0) - (posObj.offsetLeft || 0),
                                  position: "absolute",
                              }
                            : {}
                    )
                );

                this.root()
                    .find(".edui-popup-caret")
                    .css({
                        top: posObj.caretTop || 0,
                        left: posObj.caretLeft || 0,
                        position: "absolute",
                    })
                    .addClass(posObj.caretDir || "up");
            }
            this.trigger("aftershow");
        },
        hide: function () {
            this.root().css("display", "none");
            this.trigger("afterhide");
        },
        attachTo: function ($obj, posObj) {
            var me = this;
            if (!$obj.data("$mergeObj")) {
                $obj.data("$mergeObj", me.root());
                $obj.on("wrapclick", function (evt) {
                    me.show($obj, posObj);
                });
                me.register("click", $obj, function (evt) {
                    me.hide();
                });
                me.data("$mergeObj", $obj);
            }
        },
        getBodyContainer: function () {
            return this.root().find(".edui-popup-body");
        },
    });
    //scale 类
    UM.ui.define("scale", {
        tpl:
            '<div class="edui-scale" unselectable="on">' +
            '<span class="edui-scale-hand0"></span>' +
            '<span class="edui-scale-hand1"></span>' +
            '<span class="edui-scale-hand2"></span>' +
            '<span class="edui-scale-hand3"></span>' +
            '<span class="edui-scale-hand4"></span>' +
            '<span class="edui-scale-hand5"></span>' +
            '<span class="edui-scale-hand6"></span>' +
            '<span class="edui-scale-hand7"></span>' +
            "</div>",
        defaultOpt: {
            $doc: $(document),
            $wrap: $(document),
        },
        init: function (options) {
            if (options.$doc) this.defaultOpt.$doc = options.$doc;
            if (options.$wrap) this.defaultOpt.$wrap = options.$wrap;
            this.root($($.parseTmpl(this.tpl, options)));
            this.initStyle();
            this.startPos = this.prePos = { x: 0, y: 0 };
            this.dragId = -1;
            return this;
        },
        initStyle: function () {
            utils.cssRule(
                "edui-style-scale",
                ".edui-scale{display:none;position:absolute;border:1px solid #38B2CE;cursor:hand;}" +
                    ".edui-scale span{position:absolute;left:0;top:0;width:7px;height:7px;overflow:hidden;font-size:0px;display:block;background-color:#3C9DD0;}" +
                    ".edui-scale .edui-scale-hand0{cursor:nw-resize;top:0;margin-top:-4px;left:0;margin-left:-4px;}" +
                    ".edui-scale .edui-scale-hand1{cursor:n-resize;top:0;margin-top:-4px;left:50%;margin-left:-4px;}" +
                    ".edui-scale .edui-scale-hand2{cursor:ne-resize;top:0;margin-top:-4px;left:100%;margin-left:-3px;}" +
                    ".edui-scale .edui-scale-hand3{cursor:w-resize;top:50%;margin-top:-4px;left:0;margin-left:-4px;}" +
                    ".edui-scale .edui-scale-hand4{cursor:e-resize;top:50%;margin-top:-4px;left:100%;margin-left:-3px;}" +
                    ".edui-scale .edui-scale-hand5{cursor:sw-resize;top:100%;margin-top:-3px;left:0;margin-left:-4px;}" +
                    ".edui-scale .edui-scale-hand6{cursor:s-resize;top:100%;margin-top:-3px;left:50%;margin-left:-4px;}" +
                    ".edui-scale .edui-scale-hand7{cursor:se-resize;top:100%;margin-top:-3px;left:100%;margin-left:-3px;}"
            );
        },
        _eventHandler: function (e) {
            var me = this,
                $doc = me.defaultOpt.$doc;
            switch (e.type) {
                case "mousedown":
                    var hand = e.target || e.srcElement,
                        hand;
                    if (hand.className.indexOf("edui-scale-hand") != -1) {
                        me.dragId = hand.className.slice(-1);
                        me.startPos.x = me.prePos.x = e.clientX;
                        me.startPos.y = me.prePos.y = e.clientY;
                        $doc.bind("mousemove", $.proxy(me._eventHandler, me));
                    }
                    break;
                case "mousemove":
                    if (me.dragId != -1) {
                        me.updateContainerStyle(me.dragId, { x: e.clientX - me.prePos.x, y: e.clientY - me.prePos.y });
                        me.prePos.x = e.clientX;
                        me.prePos.y = e.clientY;
                        me.updateTargetElement();
                    }
                    break;
                case "mouseup":
                    if (me.dragId != -1) {
                        me.dragId = -1;
                        me.updateTargetElement();
                        var $target = me.data("$scaleTarget");
                        if ($target.parent()) me.attachTo(me.data("$scaleTarget"));
                    }
                    $doc.unbind("mousemove", $.proxy(me._eventHandler, me));
                    break;
                default:
                    break;
            }
        },
        updateTargetElement: function () {
            var me = this,
                $root = me.root(),
                $target = me.data("$scaleTarget");
            $target.css({ width: $root.width(), height: $root.height() });
            me.attachTo($target);
        },
        updateContainerStyle: function (dir, offset) {
            var me = this,
                $dom = me.root(),
                tmp,
                rect = [
                    //[left, top, width, height]
                    [0, 0, -1, -1],
                    [0, 0, 0, -1],
                    [0, 0, 1, -1],
                    [0, 0, -1, 0],
                    [0, 0, 1, 0],
                    [0, 0, -1, 1],
                    [0, 0, 0, 1],
                    [0, 0, 1, 1],
                ];

            if (rect[dir][0] != 0) {
                tmp = parseInt($dom.offset().left) + offset.x;
                $dom.css("left", me._validScaledProp("left", tmp));
            }
            if (rect[dir][1] != 0) {
                tmp = parseInt($dom.offset().top) + offset.y;
                $dom.css("top", me._validScaledProp("top", tmp));
            }
            if (rect[dir][2] != 0) {
                tmp = $dom.width() + rect[dir][2] * offset.x;
                $dom.css("width", me._validScaledProp("width", tmp));
            }
            if (rect[dir][3] != 0) {
                tmp = $dom.height() + rect[dir][3] * offset.y;
                $dom.css("height", me._validScaledProp("height", tmp));
            }
        },
        _validScaledProp: function (prop, value) {
            var $ele = this.root(),
                $wrap = this.defaultOpt.$doc,
                calc = function (val, a, b) {
                    return val + a > b ? b - a : value;
                };

            value = isNaN(value) ? 0 : value;
            switch (prop) {
                case "left":
                    return value < 0 ? 0 : calc(value, $ele.width(), $wrap.width());
                case "top":
                    return value < 0 ? 0 : calc(value, $ele.height(), $wrap.height());
                case "width":
                    return value <= 0 ? 1 : calc(value, $ele.offset().left, $wrap.width());
                case "height":
                    return value <= 0 ? 1 : calc(value, $ele.offset().top, $wrap.height());
            }
        },
        show: function ($obj) {
            var me = this;
            if ($obj) me.attachTo($obj);
            me.root().bind("mousedown", $.proxy(me._eventHandler, me));
            me.defaultOpt.$doc.bind("mouseup", $.proxy(me._eventHandler, me));
            me.root().show();
            me.trigger("aftershow");
        },
        hide: function () {
            var me = this;
            me.root().unbind("mousedown", $.proxy(me._eventHandler, me));
            me.defaultOpt.$doc.unbind("mouseup", $.proxy(me._eventHandler, me));
            me.root().hide();
            me.trigger("afterhide");
        },
        attachTo: function ($obj) {
            var me = this,
                imgPos = $obj.offset(),
                $root = me.root(),
                $wrap = me.defaultOpt.$wrap,
                posObj = $wrap.offset();

            me.data("$scaleTarget", $obj);
            me.root().css({
                position: "absolute",
                width: $obj.width(),
                height: $obj.height(),
                left: imgPos.left - posObj.left - parseInt($wrap.css("border-left-width")) - parseInt($root.css("border-left-width")),
                top: imgPos.top - posObj.top - parseInt($wrap.css("border-top-width")) - parseInt($root.css("border-top-width")),
            });
        },
        getScaleTarget: function () {
            return this.data("$scaleTarget")[0];
        },
    });
    //colorpicker 类
    UM.ui.define(
        "colorpicker",
        {
            tpl: function (opt) {
                var COLORS = (
                    "ffffff,000000,eeece1,1f497d,4f81bd,c0504d,9bbb59,8064a2,4bacc6,f79646," +
                    "f2f2f2,7f7f7f,ddd9c3,c6d9f0,dbe5f1,f2dcdb,ebf1dd,e5e0ec,dbeef3,fdeada," +
                    "d8d8d8,595959,c4bd97,8db3e2,b8cce4,e5b9b7,d7e3bc,ccc1d9,b7dde8,fbd5b5," +
                    "bfbfbf,3f3f3f,938953,548dd4,95b3d7,d99694,c3d69b,b2a2c7,92cddc,fac08f," +
                    "a5a5a5,262626,494429,17365d,366092,953734,76923c,5f497a,31859b,e36c09," +
                    "7f7f7f,0c0c0c,1d1b10,0f243e,244061,632423,4f6128,3f3151,205867,974806," +
                    "c00000,ff0000,ffc000,ffff00,92d050,00b050,00b0f0,0070c0,002060,7030a0,"
                ).split(",");

                var html =
                    '<div unselectable="on" onmousedown="return false" class="edui-colorpicker<%if (name){%> edui-colorpicker-<%=name%><%}%>" >' +
                    '<table unselectable="on" onmousedown="return false">' +
                    '<tr><td colspan="10">' +
                    opt.lang_themeColor +
                    "</td> </tr>" +
                    '<tr class="edui-colorpicker-firstrow" >';

                for (var i = 0; i < COLORS.length; i++) {
                    if (i && i % 10 === 0) {
                        html +=
                            "</tr>" +
                            (i == 60 ? '<tr><td colspan="10">' + opt.lang_standardColor + "</td></tr>" : "") +
                            "<tr" +
                            (i == 60 ? ' class="edui-colorpicker-firstrow"' : "") +
                            ">";
                    }
                    html +=
                        i < 70
                            ? '<td><a unselectable="on" onmousedown="return false" title="' +
                              COLORS[i] +
                              '" class="edui-colorpicker-colorcell"' +
                              ' data-color="#' +
                              COLORS[i] +
                              '"' +
                              ' style="background-color:#' +
                              COLORS[i] +
                              ";border:solid #ccc;" +
                              (i < 10 || i >= 60 ? "border-width:1px;" : i >= 10 && i < 20 ? "border-width:1px 1px 0 1px;" : "border-width:0 1px 0 1px;") +
                              '"' +
                              "></a></td>"
                            : "";
                }
                html += "</tr></table></div>";
                return html;
            },
            init: function (options) {
                var me = this;
                me.root($($.parseTmpl(me.supper.mergeTpl(me.tpl(options)), options)));

                me.root().on("click", function (e) {
                    me.trigger("pickcolor", $(e.target).data("color"));
                });
            },
        },
        "popup"
    );
    /**
     * Created with JetBrains PhpStorm.
     * User: hn
     * Date: 13-5-29
     * Time: 下午8:01
     * To change this template use File | Settings | File Templates.
     */

    (function () {
        var widgetName = "combobox",
            itemClassName = "edui-combobox-item",
            HOVER_CLASS = "edui-combobox-item-hover",
            ICON_CLASS = "edui-combobox-checked-icon",
            labelClassName = "edui-combobox-item-label";

        UM.ui.define(
            widgetName,
            (function () {
                return {
                    tpl:
                        '<ul class="dropdown-menu edui-combobox-menu<%if (comboboxName!==\'\') {%> edui-combobox-<%=comboboxName%><%}%>" unselectable="on" onmousedown="return false" role="menu" aria-labelledby="dropdownMenu">' +
                        "<%if(autoRecord) {%>" +
                        "<%for( var i=0, len = recordStack.length; i<len; i++ ) {%>" +
                        "<%var index = recordStack[i];%>" +
                        '<li class="<%=itemClassName%><%if( selected == index ) {%> edui-combobox-checked<%}%>" data-item-index="<%=index%>" unselectable="on" onmousedown="return false">' +
                        '<span class="edui-combobox-icon" unselectable="on" onmousedown="return false"></span>' +
                        '<label class="<%=labelClassName%>" style="<%=itemStyles[ index ]%>" unselectable="on" onmousedown="return false"><%=items[index]%></label>' +
                        "</li>" +
                        "<%}%>" +
                        "<%if( i ) {%>" +
                        '<li class="edui-combobox-item-separator"></li>' +
                        "<%}%>" +
                        "<%}%>" +
                        "<%for( var i=0, label; label = items[i]; i++ ) {%>" +
                        '<li class="<%=itemClassName%><%if( selected == i ) {%> edui-combobox-checked<%}%> edui-combobox-item-<%=i%>" data-item-index="<%=i%>" unselectable="on" onmousedown="return false">' +
                        '<span class="edui-combobox-icon" unselectable="on" onmousedown="return false"></span>' +
                        '<label class="<%=labelClassName%>" style="<%=itemStyles[ i ]%>" unselectable="on" onmousedown="return false"><%=label%></label>' +
                        "</li>" +
                        "<%}%>" +
                        "</ul>",
                    defaultOpt: {
                        //记录栈初始列表
                        recordStack: [],
                        //可用项列表
                        items: [],
                        //item对应的值列表
                        value: [],
                        comboboxName: "",
                        selected: "",
                        //自动记录
                        autoRecord: true,
                        //最多记录条数
                        recordCount: 5,
                    },
                    init: function (options) {
                        var me = this;

                        $.extend(me._optionAdaptation(options), me._createItemMapping(options.recordStack, options.items), {
                            itemClassName: itemClassName,
                            iconClass: ICON_CLASS,
                            labelClassName: labelClassName,
                        });

                        this._transStack(options);

                        me.root($($.parseTmpl(me.tpl, options)));

                        this.data("options", options).initEvent();
                    },
                    initEvent: function () {
                        var me = this;

                        me.initSelectItem();

                        this.initItemActive();
                    },
                    /**
                     * 初始化选择项
                     */
                    initSelectItem: function () {
                        var me = this,
                            labelClass = "." + labelClassName;

                        me.root().delegate("." + itemClassName, "click", function () {
                            var $li = $(this),
                                index = $li.attr("data-item-index");

                            me.trigger("comboboxselect", {
                                index: index,
                                label: $li.find(labelClass).text(),
                                value: me.data("options").value[index],
                            }).select(index);

                            me.hide();

                            return false;
                        });
                    },
                    initItemActive: function () {
                        var fn = {
                            mouseenter: "addClass",
                            mouseleave: "removeClass",
                        };
                        if ($.IE6) {
                            this.root()
                                .delegate("." + itemClassName, "mouseenter mouseleave", function (evt) {
                                    $(this)[fn[evt.type]](HOVER_CLASS);
                                })
                                .one("afterhide", function () {});
                        }
                    },
                    /**
                     * 选择给定索引的项
                     * @param index 项索引
                     * @returns {*} 如果存在对应索引的项，则返回该项；否则返回null
                     */
                    select: function (index) {
                        var itemCount = this.data("options").itemCount,
                            items = this.data("options").autowidthitem;

                        if (items && !items.length) {
                            items = this.data("options").items;
                        }

                        if (itemCount == 0) {
                            return null;
                        }

                        if (index < 0) {
                            index = itemCount + (index % itemCount);
                        } else if (index >= itemCount) {
                            index = itemCount - 1;
                        }

                        this.trigger("changebefore", items[index]);

                        this._update(index);

                        this.trigger("changeafter", items[index]);

                        return null;
                    },
                    selectItemByLabel: function (label) {
                        var itemMapping = this.data("options").itemMapping,
                            me = this,
                            index = null;

                        !$.isArray(label) && (label = [label]);

                        $.each(label, function (i, item) {
                            index = itemMapping[item];

                            if (index !== undefined) {
                                me.select(index);
                                return false;
                            }
                        });
                    },
                    /**
                     * 转换记录栈
                     */
                    _transStack: function (options) {
                        var temp = [],
                            itemIndex = -1,
                            selected = -1;

                        $.each(options.recordStack, function (index, item) {
                            itemIndex = options.itemMapping[item];

                            if ($.isNumeric(itemIndex)) {
                                temp.push(itemIndex);

                                //selected的合法性检测
                                if (item == options.selected) {
                                    selected = itemIndex;
                                }
                            }
                        });

                        options.recordStack = temp;
                        options.selected = selected;
                        temp = null;
                    },
                    _optionAdaptation: function (options) {
                        if (!("itemStyles" in options)) {
                            options.itemStyles = [];

                            for (var i = 0, len = options.items.length; i < len; i++) {
                                options.itemStyles.push("");
                            }
                        }

                        options.autowidthitem = options.autowidthitem || options.items;
                        options.itemCount = options.items.length;

                        return options;
                    },
                    _createItemMapping: function (stackItem, items) {
                        var temp = {},
                            result = {
                                recordStack: [],
                                mapping: {},
                            };

                        $.each(items, function (index, item) {
                            temp[item] = index;
                        });

                        result.itemMapping = temp;

                        $.each(stackItem, function (index, item) {
                            if (temp[item] !== undefined) {
                                result.recordStack.push(temp[item]);
                                result.mapping[item] = temp[item];
                            }
                        });

                        return result;
                    },
                    _update: function (index) {
                        var options = this.data("options"),
                            newStack = [],
                            newChilds = null;

                        $.each(options.recordStack, function (i, item) {
                            if (item != index) {
                                newStack.push(item);
                            }
                        });

                        //压入最新的记录
                        newStack.unshift(index);

                        if (newStack.length > options.recordCount) {
                            newStack.length = options.recordCount;
                        }

                        options.recordStack = newStack;
                        options.selected = index;

                        newChilds = $($.parseTmpl(this.tpl, options));

                        //重新渲染
                        this.root().html(newChilds.html());

                        newChilds = null;
                        newStack = null;
                    },
                };
            })(),
            "menu"
        );
    })();

    /**
     * Combox 抽象基类
     * User: hn
     * Date: 13-5-29
     * Time: 下午8:01
     * To change this template use File | Settings | File Templates.
     */

    (function () {
        var widgetName = "buttoncombobox";

        UM.ui.define(
            widgetName,
            (function () {
                return {
                    defaultOpt: {
                        //按钮初始文字
                        label: "",
                        title: "",
                    },
                    init: function (options) {
                        var me = this;

                        var btnWidget = $.eduibutton({
                            caret: true,
                            name: options.comboboxName,
                            title: options.title,
                            text: options.label,
                            click: function () {
                                me.show(this.root());
                            },
                        });

                        me.supper.init.call(me, options);

                        //监听change， 改变button显示内容
                        me.on("changebefore", function (e, label) {
                            btnWidget.eduibutton("label", label);
                        });

                        me.data("button", btnWidget);

                        me.attachTo(btnWidget);
                    },
                    button: function () {
                        return this.data("button");
                    },
                };
            })(),
            "combobox"
        );
    })();

    /*modal 类*/
    UM.ui.define("modal", {
        tpl:
            '<div class="edui-modal" tabindex="-1" >' +
            '<div class="edui-modal-header">' +
            '<div class="edui-close" data-hide="modal"></div>' +
            '<h3 class="edui-title"><%=title%></h3>' +
            "</div>" +
            '<div class="edui-modal-body"  style="<%if(width){%>width:<%=width%>px;<%}%>' +
            '<%if(height){%>height:<%=height%>px;<%}%>">' +
            " </div>" +
            "<% if(cancellabel || oklabel) {%>" +
            '<div class="edui-modal-footer">' +
            '<div class="edui-modal-tip"></div>' +
            '<%if(oklabel){%><div class="edui-btn edui-btn-primary" data-ok="modal"><%=oklabel%></div><%}%>' +
            '<%if(cancellabel){%><div class="edui-btn" data-hide="modal"><%=cancellabel%></div><%}%>' +
            "</div>" +
            "<%}%></div>",
        defaultOpt: {
            title: "",
            cancellabel: "",
            oklabel: "",
            width: "",
            height: "",
            backdrop: true,
            keyboard: true,
        },
        init: function (options) {
            var me = this;

            me.root($($.parseTmpl(me.tpl, options || {})));

            me.data("options", options);
            if (options.okFn) {
                me.on("ok", $.proxy(options.okFn, me));
            }
            if (options.cancelFn) {
                me.on("beforehide", $.proxy(options.cancelFn, me));
            }

            me.root().delegate('[data-hide="modal"]', "click", $.proxy(me.hide, me)).delegate('[data-ok="modal"]', "click", $.proxy(me.ok, me));

            $('[data-hide="modal"],[data-ok="modal"]', me.root()).hover(function () {
                $(this).toggleClass("edui-hover");
            });
        },
        toggle: function () {
            var me = this;
            return me[!me.data("isShown") ? "show" : "hide"]();
        },
        show: function () {
            var me = this;

            me.trigger("beforeshow");

            if (me.data("isShown")) return;

            me.data("isShown", true);

            me.escape();

            me.backdrop(function () {
                me.autoCenter();
                me.root().show().focus().trigger("aftershow");
            });
        },
        showTip: function (text) {
            $(".edui-modal-tip", this.root()).html(text).fadeIn();
        },
        hideTip: function (text) {
            $(".edui-modal-tip", this.root()).fadeOut(function () {
                $(this).html("");
            });
        },
        autoCenter: function () {
            //ie6下不用处理了
            !$.IE6 && this.root().css("margin-left", -(this.root().width() / 2));
        },
        hide: function () {
            var me = this;

            me.trigger("beforehide");

            if (!me.data("isShown")) return;

            me.data("isShown", false);

            me.escape();

            me.hideModal();
        },
        escape: function () {
            var me = this;
            if (me.data("isShown") && me.data("options").keyboard) {
                me.root().on("keyup", function (e) {
                    e.which == 27 && me.hide();
                });
            } else if (!me.data("isShown")) {
                me.root().off("keyup");
            }
        },
        hideModal: function () {
            var me = this;
            me.root().hide();
            me.backdrop(function () {
                me.removeBackdrop();
                me.trigger("afterhide");
            });
        },
        removeBackdrop: function () {
            this.$backdrop && this.$backdrop.remove();
            this.$backdrop = null;
        },
        backdrop: function (callback) {
            var me = this;
            if (me.data("isShown") && me.data("options").backdrop) {
                me.$backdrop = $('<div class="edui-modal-backdrop" />').click(
                    me.data("options").backdrop == "static" ? $.proxy(me.root()[0].focus, me.root()[0]) : $.proxy(me.hide, me)
                );
            }
            me.trigger("afterbackdrop");
            callback && callback();
        },
        attachTo: function ($obj) {
            var me = this;
            if (!$obj.data("$mergeObj")) {
                $obj.data("$mergeObj", me.root());
                $obj.on("click", function () {
                    me.toggle($obj);
                });
                me.data("$mergeObj", $obj);
            }
        },
        ok: function () {
            var me = this;
            me.trigger("beforeok");
            if (me.trigger("ok", me) === false) {
                return;
            }
            me.hide();
        },
        getBodyContainer: function () {
            return this.root().find(".edui-modal-body");
        },
    });

    /*tooltip 类*/
    UM.ui.define("tooltip", {
        tpl:
            '<div class="edui-tooltip" unselectable="on" onmousedown="return false">' +
            '<div class="edui-tooltip-arrow" unselectable="on" onmousedown="return false"></div>' +
            '<div class="edui-tooltip-inner" unselectable="on" onmousedown="return false"></div>' +
            "</div>",
        init: function (options) {
            var me = this;
            me.root($($.parseTmpl(me.tpl, options || {})));
        },
        content: function (e) {
            var me = this,
                title = $(e.currentTarget).attr("data-original-title");

            me.root().find(".edui-tooltip-inner")["text"](title);
        },
        position: function (e) {
            var me = this,
                $obj = $(e.currentTarget);

            me.root().css(
                $.extend(
                    { display: "block" },
                    $obj
                        ? {
                              top: $obj.outerHeight(),
                              left: ($obj.outerWidth() - me.root().outerWidth()) / 2,
                          }
                        : {}
                )
            );
        },
        show: function (e) {
            if ($(e.currentTarget).hasClass("edui-disabled")) return;

            var me = this;
            me.content(e);
            me.root().appendTo($(e.currentTarget));
            me.position(e);
            me.root().css("display", "block");
        },
        hide: function () {
            var me = this;
            me.root().css("display", "none");
        },
        attachTo: function ($obj) {
            var me = this;

            function tmp($obj) {
                var me = this;

                if (!$.contains(document.body, me.root()[0])) {
                    me.root().appendTo($obj);
                }

                me.data("tooltip", me.root());

                $obj.each(function () {
                    if ($(this).attr("data-original-title")) {
                        $(this).on("mouseenter", $.proxy(me.show, me)).on("mouseleave click", $.proxy(me.hide, me));
                    }
                });
            }

            if ($.type($obj) === "undefined") {
                $("[data-original-title]").each(function (i, el) {
                    tmp.call(me, $(el));
                });
            } else {
                if (!$obj.data("tooltip")) {
                    tmp.call(me, $obj);
                }
            }
        },
    });

    /*tab 类*/
    UM.ui.define("tab", {
        init: function (options) {
            var me = this,
                slr = options.selector;

            if ($.type(slr)) {
                me.root($(slr, options.context));
                me.data("context", options.context);

                $(slr, me.data("context")).on("click", function (e) {
                    me.show(e);
                });
            }
        },
        show: function (e) {
            var me = this,
                $cur = $(e.target),
                $ul = $cur.closest("ul"),
                selector,
                previous,
                $target,
                e;

            selector = $cur.attr("data-context");
            selector = selector && selector.replace(/.*(?=#[^\s]*$)/, "");

            var $tmp = $cur.parent("li");

            if (!$tmp.length || $tmp.hasClass("edui-active")) return;

            previous = $ul.find(".edui-active:last a")[0];

            e = $.Event("beforeshow", {
                target: $cur[0],
                relatedTarget: previous,
            });

            me.trigger(e);

            if (e.isDefaultPrevented()) return;

            $target = $(selector, me.data("context"));

            me.activate($cur.parent("li"), $ul);
            me.activate($target, $target.parent(), function () {
                me.trigger({
                    type: "aftershow",
                    relatedTarget: previous,
                });
            });
        },
        activate: function (element, container, callback) {
            if (element === undefined) {
                return $(".edui-tab-item.edui-active", this.root()).index();
            }

            var $active = container.find("> .edui-active");

            $active.removeClass("edui-active");

            element.addClass("edui-active");

            callback && callback();
        },
    });

    //button 类
    UM.ui.define("separator", {
        tpl: '<div class="edui-separator" unselectable="on" onmousedown="return false" ></div>',
        init: function (options) {
            var me = this;
            me.root($($.parseTmpl(me.tpl, options)));
            return me;
        },
    });
    /**
     * @file adapter.js
     * @desc adapt ui to editor
     * @import core/Editor.js, core/utils.js
     */

    (function () {
        var _editorUI = {},
            _editors = {},
            _readyFn = [],
            _activeWidget = null,
            _widgetData = {},
            _widgetCallBack = {},
            _cacheUI = {},
            _maxZIndex = null;

        utils.extend(UM, {
            defaultWidth: 500,
            defaultHeight: 500,
            registerUI: function (name, fn) {
                utils.each(name.split(/\s+/), function (uiname) {
                    _editorUI[uiname] = fn;
                });
            },

            setEditor: function (editor) {
                !_editors[editor.id] && (_editors[editor.id] = editor);
            },
            registerWidget: function (name, pro, cb) {
                _widgetData[name] = $.extend2(pro, {
                    $root: "",
                    _preventDefault: false,
                    root: function ($el) {
                        return this.$root || (this.$root = $el);
                    },
                    preventDefault: function () {
                        this._preventDefault = true;
                    },
                    clear: false,
                });
                if (cb) {
                    _widgetCallBack[name] = cb;
                }
            },
            getWidgetData: function (name) {
                return _widgetData[name];
            },
            setWidgetBody: function (name, $widget, editor) {
                if (!editor._widgetData) {
                    utils.extend(editor, {
                        _widgetData: {},
                        getWidgetData: function (name) {
                            return this._widgetData[name];
                        },
                        getWidgetCallback: function (widgetName) {
                            var me = this;
                            return function () {
                                return _widgetCallBack[widgetName].apply(me, [me, $widget].concat(Array.prototype.slice.call(arguments, 0)));
                            };
                        },
                    });
                }
                var pro = _widgetData[name];
                if (!pro) {
                    return null;
                }
                pro = editor._widgetData[name];
                if (!pro) {
                    pro = _widgetData[name];
                    pro = editor._widgetData[name] = $.type(pro) == "function" ? pro : utils.clone(pro);
                }

                pro.root($widget.edui().getBodyContainer());

                pro.initContent(editor, $widget);
                if (!pro._preventDefault) {
                    pro.initEvent(editor, $widget);
                }

                pro.width && $widget.width(pro.width);
            },
            setActiveWidget: function ($widget) {
                _activeWidget = $widget;
            },
            getEditor: function (id, options) {
                var editor = _editors[id] || (_editors[id] = this.createEditor(id, options));
                _maxZIndex = _maxZIndex ? Math.max(editor.getOpt("zIndex"), _maxZIndex) : editor.getOpt("zIndex");
                return editor;
            },
            setTopEditor: function (editor) {
                $.each(_editors, function (i, o) {
                    if (editor == o) {
                        editor.$container && editor.$container.css("zIndex", _maxZIndex + 1);
                    } else {
                        o.$container && o.$container.css("zIndex", o.getOpt("zIndex"));
                    }
                });
            },
            clearCache: function (id) {
                if (_editors[id]) {
                    delete _editors[id];
                }
            },
            delEditor: function (id) {
                var editor;
                if ((editor = _editors[id])) {
                    editor.destroy();
                }
            },
            ready: function (fn) {
                _readyFn.push(fn);
            },
            createEditor: function (id, opt) {
                var editor = new UM.Editor(opt);
                var T = this;

                editor.langIsReady ? $.proxy(renderUI, T)() : editor.addListener("langReady", $.proxy(renderUI, T));
                function renderUI() {
                    var $container = this.createUI("#" + id, editor);
                    editor.key = id;
                    editor.ready(function () {
                        $.each(_readyFn, function (index, fn) {
                            $.proxy(fn, editor)();
                        });
                    });
                    var options = editor.options;
                    if (options.initialFrameWidth) {
                        options.minFrameWidth = options.initialFrameWidth;
                    } else {
                        options.minFrameWidth = options.initialFrameWidth = editor.$body.width() || UM.defaultWidth;
                    }

                    $container.css({
                        width: options.initialFrameWidth,
                        zIndex: editor.getOpt("zIndex"),
                    });

                    //ie6下缓存图片
                    UM.browser.ie && UM.browser.version === 6 && document.execCommand("BackgroundImageCache", false, true);

                    editor.render(id);

                    //添加tooltip;
                    $.eduitooltip && $.eduitooltip("attachTo", $("[data-original-title]", $container)).css("z-index", editor.getOpt("zIndex") + 1);

                    $container.find("a").click(function (evt) {
                        evt.preventDefault();
                    });

                    editor.fireEvent("afteruiready");
                }

                return editor;
            },
            createUI: function (id, editor) {
                var $editorCont = $(id),
                    $container = $('<div class="edui-container"><div class="edui-editor-body"></div></div>').insertBefore($editorCont);
                editor.$container = $container;
                editor.container = $container[0];

                editor.$body = $editorCont;

                //修正在ie9+以上的版本中，自动长高收起时的，残影问题
                if (browser.ie && browser.ie9above) {
                    var $span = $('<span style="padding:0;margin:0;height:0;width:0"></span>');
                    $span.insertAfter($container);
                }
                //初始化注册的ui组件
                $.each(_editorUI, function (n, v) {
                    var widget = v.call(editor, n);
                    if (widget) {
                        _cacheUI[n] = widget;
                    }
                });

                $container.find(".edui-editor-body").append($editorCont).before(this.createToolbar(editor.options, editor));

                $container.find(".edui-toolbar").append($('<div class="edui-dialog-container"></div>'));

                return $container;
            },
            createToolbar: function (options, editor) {
                var $toolbar = $.eduitoolbar(),
                    toolbar = $toolbar.edui();
                //创建下来菜单列表

                if (options.toolbar && options.toolbar.length) {
                    var btns = [];
                    $.each(options.toolbar, function (i, uiNames) {
                        $.each(uiNames.split(/\s+/), function (index, name) {
                            if (name == "|") {
                                $.eduiseparator && btns.push($.eduiseparator());
                            } else {
                                var ui = _cacheUI[name];
                                if (name == "fullscreen") {
                                    ui && btns.unshift(ui);
                                } else {
                                    ui && btns.push(ui);
                                }
                            }
                        });
                        btns.length && toolbar.appendToBtnmenu(btns);
                    });
                } else {
                    $toolbar.find(".edui-btn-toolbar").remove();
                }
                return $toolbar;
            },
        });
    })();

    UM.registerUI(
        "bold italic redo undo underline strikethrough superscript subscript insertorderedlist insertunorderedlist " +
            "cleardoc selectall link unlink print preview justifyleft justifycenter justifyright justifyfull removeformat horizontal drafts",
        function (name) {
            var me = this;
            var $btn = $.eduibutton({
                icon: name,
                click: function () {
                    me.execCommand(name);
                },
                title: this.getLang("labelMap")[name] || "",
            });

            this.addListener("selectionchange", function () {
                var state = this.queryCommandState(name);
                $btn.edui()
                    .disabled(state == -1)
                    .active(state == 1);
            });
            return $btn;
        }
    );

    /**
     * 全屏组件
     */

    (function () {
        //状态缓存
        var STATUS_CACHE = {},
            //状态值列表
            STATUS_LIST = ["width", "height", "position", "top", "left", "margin", "padding", "overflowX", "overflowY"],
            CONTENT_AREA_STATUS = {},
            //页面状态
            DOCUMENT_STATUS = {},
            DOCUMENT_ELEMENT_STATUS = {},
            FULLSCREENS = {};

        UM.registerUI("fullscreen", function (name) {
            var me = this,
                $button = $.eduibutton({
                    icon: "fullscreen",
                    title: (me.options.labelMap && me.options.labelMap[name]) || me.getLang("labelMap." + name),
                    click: function () {
                        //切换
                        me.execCommand(name);
                        UM.setTopEditor(me);
                    },
                });

            me.addListener("selectionchange", function () {
                var state = this.queryCommandState(name);
                $button
                    .edui()
                    .disabled(state == -1)
                    .active(state == 1);
            });

            //切换至全屏
            me.addListener("ready", function () {
                me.options.fullscreen && Fullscreen.getInstance(me).toggle();
            });

            return $button;
        });

        UM.commands["fullscreen"] = {
            execCommand: function (cmdName) {
                Fullscreen.getInstance(this).toggle();
            },
            queryCommandState: function (cmdName) {
                return this._edui_fullscreen_status;
            },
            notNeedUndo: 1,
        };

        function Fullscreen(editor) {
            var me = this;

            if (!editor) {
                throw new Error("invalid params, notfound editor");
            }

            me.editor = editor;

            //记录初始化的全屏组件
            FULLSCREENS[editor.uid] = this;

            editor.addListener("destroy", function () {
                delete FULLSCREENS[editor.uid];
                me.editor = null;
            });
        }

        Fullscreen.prototype = {
            /**
             * 全屏状态切换
             */
            toggle: function () {
                var editor = this.editor,
                    //当前编辑器的缩放状态
                    _edui_fullscreen_status = this.isFullState();
                editor.fireEvent("beforefullscreenchange", !_edui_fullscreen_status);

                //更新状态
                this.update(!_edui_fullscreen_status);

                !_edui_fullscreen_status ? this.enlarge() : this.revert();

                editor.fireEvent("afterfullscreenchange", !_edui_fullscreen_status);
                if (editor.body.contentEditable === "true") {
                    editor.fireEvent("fullscreenchanged", !_edui_fullscreen_status);
                }

                editor.fireEvent("selectionchange");
            },
            /**
             * 执行放大
             */
            enlarge: function () {
                this.saveSataus();

                this.setDocumentStatus();

                this.resize();
            },
            /**
             * 全屏还原
             */
            revert: function () {
                //还原CSS表达式
                var options = this.editor.options,
                    height = /%$/.test(options.initialFrameHeight)
                        ? "100%"
                        : options.initialFrameHeight - this.getStyleValue("padding-top") - this.getStyleValue("padding-bottom") - this.getStyleValue("border-width");

                $.IE6 && this.getEditorHolder().style.setExpression("height", "this.scrollHeight <= " + height + ' ? "' + height + 'px" : "auto"');

                //还原容器状态
                this.revertContainerStatus();

                this.revertContentAreaStatus();

                this.revertDocumentStatus();
            },
            /**
             * 更新状态
             * @param isFull 当前状态是否是全屏状态
             */
            update: function (isFull) {
                this.editor._edui_fullscreen_status = isFull;
            },
            /**
             * 调整当前编辑器的大小, 如果当前编辑器不处于全屏状态， 则不做调整
             */
            resize: function () {
                var $win = null,
                    height = 0,
                    width = 0,
                    borderWidth = 0,
                    paddingWidth = 0,
                    editor = this.editor,
                    me = this,
                    bound = null,
                    editorBody = null;

                if (!this.isFullState()) {
                    return;
                }

                $win = $(window);
                width = $win.width();
                height = $win.height();
                editorBody = this.getEditorHolder();
                //文本编辑区border宽度
                borderWidth = parseInt(domUtils.getComputedStyle(editorBody, "border-width"), 10) || 0;
                //容器border宽度
                borderWidth += parseInt(domUtils.getComputedStyle(editor.container, "border-width"), 10) || 0;
                //容器padding
                paddingWidth += parseInt(domUtils.getComputedStyle(editorBody, "padding-left"), 10) + parseInt(domUtils.getComputedStyle(editorBody, "padding-right"), 10) || 0;

                //干掉css表达式
                $.IE6 && editorBody.style.setExpression("height", null);

                bound = this.getBound();

                $(editor.container).css({
                    width: width + "px",
                    height: height + "px",
                    position: !$.IE6 ? "fixed" : "absolute",
                    top: bound.top,
                    left: bound.left,
                    margin: 0,
                    padding: 0,
                    overflowX: "hidden",
                    overflowY: "hidden",
                });

                $(editorBody).css({
                    width: width - 2 * borderWidth - paddingWidth + "px",
                    height:
                        height -
                        2 * borderWidth -
                        (editor.options.withoutToolbar ? 0 : $(".edui-toolbar", editor.container).outerHeight()) -
                        $(".edui-bottombar", editor.container).outerHeight() +
                        "px",
                    overflowX: "hidden",
                    overflowY: "auto",
                });
            },
            /**
             * 保存状态
             */
            saveSataus: function () {
                var styles = this.editor.container.style,
                    tmp = null,
                    cache = {};

                for (var i = 0, len = STATUS_LIST.length; i < len; i++) {
                    tmp = STATUS_LIST[i];
                    cache[tmp] = styles[tmp];
                }

                STATUS_CACHE[this.editor.uid] = cache;

                this.saveContentAreaStatus();
                this.saveDocumentStatus();
            },
            saveContentAreaStatus: function () {
                var $holder = $(this.getEditorHolder());

                CONTENT_AREA_STATUS[this.editor.uid] = {
                    width: $holder.css("width"),
                    overflowX: $holder.css("overflowX"),
                    overflowY: $holder.css("overflowY"),
                    height: $holder.css("height"),
                };
            },
            /**
             * 保存与指定editor相关的页面的状态
             */
            saveDocumentStatus: function () {
                var $doc = $(this.getEditorDocumentBody());

                DOCUMENT_STATUS[this.editor.uid] = {
                    overflowX: $doc.css("overflowX"),
                    overflowY: $doc.css("overflowY"),
                };
                DOCUMENT_ELEMENT_STATUS[this.editor.uid] = {
                    overflowX: $(this.getEditorDocumentElement()).css("overflowX"),
                    overflowY: $(this.getEditorDocumentElement()).css("overflowY"),
                };
            },
            /**
             * 恢复容器状态
             */
            revertContainerStatus: function () {
                $(this.editor.container).css(this.getEditorStatus());
            },
            /**
             * 恢复编辑区状态
             */
            revertContentAreaStatus: function () {
                var holder = this.getEditorHolder(),
                    state = this.getContentAreaStatus();

                if (this.supportMin()) {
                    delete state.height;
                    holder.style.height = null;
                }

                $(holder).css(state);
            },
            /**
             * 恢复页面状态
             */
            revertDocumentStatus: function () {
                var status = this.getDocumentStatus();
                $(this.getEditorDocumentBody()).css("overflowX", status.body.overflowX);
                $(this.getEditorDocumentElement()).css("overflowY", status.html.overflowY);
            },
            setDocumentStatus: function () {
                $(this.getEditorDocumentBody()).css({
                    overflowX: "hidden",
                    overflowY: "hidden",
                });
                $(this.getEditorDocumentElement()).css({
                    overflowX: "hidden",
                    overflowY: "hidden",
                });
            },
            /**
             * 检测当前编辑器是否处于全屏状态全屏状态
             * @returns {boolean} 是否处于全屏状态
             */
            isFullState: function () {
                return !!this.editor._edui_fullscreen_status;
            },
            /**
             * 获取编辑器状态
             */
            getEditorStatus: function () {
                return STATUS_CACHE[this.editor.uid];
            },
            getContentAreaStatus: function () {
                return CONTENT_AREA_STATUS[this.editor.uid];
            },
            getEditorDocumentElement: function () {
                return this.editor.container.ownerDocument.documentElement;
            },
            getEditorDocumentBody: function () {
                return this.editor.container.ownerDocument.body;
            },
            /**
             * 获取编辑区包裹对象
             */
            getEditorHolder: function () {
                return this.editor.body;
            },
            /**
             * 获取编辑器状态
             * @returns {*}
             */
            getDocumentStatus: function () {
                return {
                    body: DOCUMENT_STATUS[this.editor.uid],
                    html: DOCUMENT_ELEMENT_STATUS[this.editor.uid],
                };
            },
            supportMin: function () {
                var node = null;

                if (!this._support) {
                    node = document.createElement("div");

                    this._support = "minWidth" in node.style;

                    node = null;
                }

                return this._support;
            },
            getBound: function () {
                var tags = {
                        html: true,
                        body: true,
                    },
                    result = {
                        top: 0,
                        left: 0,
                    },
                    offsetParent = null;

                if (!$.IE6) {
                    return result;
                }

                offsetParent = this.editor.container.offsetParent;

                if (offsetParent && !tags[offsetParent.nodeName.toLowerCase()]) {
                    tags = offsetParent.getBoundingClientRect();
                    result.top = -tags.top;
                    result.left = -tags.left;
                }

                return result;
            },
            getStyleValue: function (attr) {
                return parseInt(domUtils.getComputedStyle(this.getEditorHolder(), attr));
            },
        };

        $.extend(Fullscreen, {
            /**
             * 监听resize
             */
            listen: function () {
                var timer = null;

                if (Fullscreen._hasFullscreenListener) {
                    return;
                }

                Fullscreen._hasFullscreenListener = true;

                $(window).on("resize", function () {
                    if (timer !== null) {
                        window.clearTimeout(timer);
                        timer = null;
                    }

                    timer = window.setTimeout(function () {
                        for (var key in FULLSCREENS) {
                            FULLSCREENS[key].resize();
                        }

                        timer = null;
                    }, 50);
                });
            },

            getInstance: function (editor) {
                if (!FULLSCREENS[editor.uid]) {
                    new Fullscreen(editor);
                }

                return FULLSCREENS[editor.uid];
            },
        });

        //开始监听
        Fullscreen.listen();
    })();
    UM.registerUI("link image video map formula", function (name) {
        var me = this,
            currentRange,
            $dialog,
            opt = {
                title: (me.options.labelMap && me.options.labelMap[name]) || me.getLang("labelMap." + name),
                url: me.options.UMEDITOR_HOME_URL + "dialogs/" + name + "/" + name + ".js",
            };

        var $btn = $.eduibutton({
            icon: name,
            title: this.getLang("labelMap")[name] || "",
        });
        //加载模版数据
        utils.loadFile(
            document,
            {
                src: opt.url,
                tag: "script",
                type: "text/javascript",
                defer: "defer",
            },
            function () {
                //调整数据
                var data = UM.getWidgetData(name);
                if (!data) return;
                if (data.buttons) {
                    var ok = data.buttons.ok;
                    if (ok) {
                        opt.oklabel = ok.label || me.getLang("ok");
                        if (ok.exec) {
                            opt.okFn = function () {
                                return $.proxy(ok.exec, null, me, $dialog)();
                            };
                        }
                    }
                    var cancel = data.buttons.cancel;
                    if (cancel) {
                        opt.cancellabel = cancel.label || me.getLang("cancel");
                        if (cancel.exec) {
                            opt.cancelFn = function () {
                                return $.proxy(cancel.exec, null, me, $dialog)();
                            };
                        }
                    }
                }
                data.width && (opt.width = data.width);
                data.height && (opt.height = data.height);

                $dialog = $.eduimodal(opt);

                $dialog
                    .attr("id", "edui-dialog-" + name)
                    .addClass("edui-dialog-" + name)
                    .find(".edui-modal-body")
                    .addClass("edui-dialog-" + name + "-body");

                $dialog
                    .edui()
                    .on("beforehide", function () {
                        var rng = me.selection.getRange();
                        if (rng.equals(currentRange)) {
                            rng.select();
                        }
                    })
                    .on("beforeshow", function () {
                        var $root = this.root(),
                            win = null,
                            offset = null;
                        currentRange = me.selection.getRange();
                        if (!$root.parent()[0]) {
                            me.$container.find(".edui-dialog-container").append($root);
                        }

                        //IE6下 特殊处理, 通过计算进行定位
                        if ($.IE6) {
                            win = {
                                width: $(window).width(),
                                height: $(window).height(),
                            };
                            offset = $root.parents(".edui-toolbar")[0].getBoundingClientRect();
                            $root.css({
                                position: "absolute",
                                margin: 0,
                                left: (win.width - $root.width()) / 2 - offset.left,
                                top: 100 - offset.top,
                            });
                        }
                        UM.setWidgetBody(name, $dialog, me);
                        UM.setTopEditor(me);
                    })
                    .on("afterbackdrop", function () {
                        this.$backdrop.css("zIndex", me.getOpt("zIndex") + 1).appendTo(me.$container.find(".edui-dialog-container"));
                        $dialog.css("zIndex", me.getOpt("zIndex") + 2);
                    })
                    .on("beforeok", function () {
                        try {
                            currentRange.select();
                        } catch (e) {}
                    })
                    .attachTo($btn);
            }
        );

        me.addListener("selectionchange", function () {
            var state = this.queryCommandState(name);
            $btn.edui()
                .disabled(state == -1)
                .active(state == 1);
        });
        return $btn;
    });
    UM.registerUI("emotion formula", function (name) {
        var me = this,
            url = me.options.UMEDITOR_HOME_URL + "dialogs/" + name + "/" + name + ".js";

        var $btn = $.eduibutton({
            icon: name,
            title: this.getLang("labelMap")[name] || "",
        });

        //加载模版数据
        utils.loadFile(
            document,
            {
                src: url,
                tag: "script",
                type: "text/javascript",
                defer: "defer",
            },
            function () {
                var opt = {
                    url: url,
                };
                //调整数据
                var data = UM.getWidgetData(name);

                data.width && (opt.width = data.width);
                data.height && (opt.height = data.height);

                $.eduipopup(opt)
                    .css("zIndex", me.options.zIndex + 1)
                    .addClass("edui-popup-" + name)
                    .edui()
                    .on("beforeshow", function () {
                        var $root = this.root();
                        if (!$root.parent().length) {
                            me.$container.find(".edui-dialog-container").append($root);
                        }
                        UM.setWidgetBody(name, $root, me);
                        UM.setTopEditor(me);
                    })
                    .attachTo($btn, {
                        offsetTop: -5,
                        offsetLeft: 10,
                        caretLeft: 11,
                        caretTop: -8,
                    });
                me.addListener("selectionchange", function () {
                    var state = this.queryCommandState(name);
                    $btn.edui()
                        .disabled(state == -1)
                        .active(state == 1);
                });
            }
        );
        return $btn;
    });
    UM.registerUI("imagescale", function () {
        var me = this,
            $imagescale;

        me.setOpt("imageScaleEnabled", true);

        if (browser.webkit && me.getOpt("imageScaleEnabled")) {
            me.addListener("click", function (type, e) {
                var range = me.selection.getRange(),
                    img = range.getClosedNode(),
                    target = e.target;

                /* 点击第一个图片的后面,八个角不消失 fix:3652 */
                if (img && img.tagName == "IMG" && target == img) {
                    if (!$imagescale) {
                        $imagescale = $.eduiscale({ $wrap: me.$container }).css("zIndex", me.options.zIndex);
                        me.$container.append($imagescale);

                        var _keyDownHandler = function () {
                                $imagescale.edui().hide();
                            },
                            _mouseDownHandler = function (e) {
                                var ele = e.target || e.srcElement;
                                if (ele && ele.className.indexOf("edui-scale") == -1) {
                                    _keyDownHandler(e);
                                }
                            },
                            timer;

                        $imagescale
                            .edui()
                            .on("aftershow", function () {
                                $(document).bind("keydown", _keyDownHandler);
                                $(document).bind("mousedown", _mouseDownHandler);
                                me.selection.getNative().removeAllRanges();
                            })
                            .on("afterhide", function () {
                                $(document).unbind("keydown", _keyDownHandler);
                                $(document).unbind("mousedown", _mouseDownHandler);
                                var target = $imagescale.edui().getScaleTarget();
                                if (target.parentNode) {
                                    me.selection.getRange().selectNode(target).select();
                                }
                            })
                            .on("mousedown", function (e) {
                                me.selection.getNative().removeAllRanges();
                                var ele = e.target || e.srcElement;
                                if (ele && ele.className.indexOf("edui-scale-hand") == -1) {
                                    timer = setTimeout(function () {
                                        $imagescale.edui().hide();
                                    }, 200);
                                }
                            })
                            .on("mouseup", function (e) {
                                var ele = e.target || e.srcElement;
                                if (ele && ele.className.indexOf("edui-scale-hand") == -1) {
                                    clearTimeout(timer);
                                }
                            });
                    }
                    $imagescale.edui().show($(img));
                } else {
                    if ($imagescale && $imagescale.css("display") != "none") $imagescale.edui().hide();
                }
            });

            me.addListener("click", function (type, e) {
                if (e.target.tagName == "IMG") {
                    var range = new dom.Range(me.document, me.body);
                    range.selectNode(e.target).select();
                }
            });
        }
    });
    UM.registerUI("autofloat", function () {
        var me = this,
            lang = me.getLang();
        me.setOpt({
            autoFloatEnabled: true,
            topOffset: 0,
        });
        var optsAutoFloatEnabled = me.options.autoFloatEnabled !== false,
            topOffset = me.options.topOffset;

        //如果不固定toolbar的位置，则直接退出
        if (!optsAutoFloatEnabled) {
            return;
        }
        me.ready(function () {
            var LteIE6 = browser.ie && browser.version <= 6,
                quirks = browser.quirks;

            function checkHasUI() {
                if (!UM.ui) {
                    alert(lang.autofloatMsg);
                    return 0;
                }
                return 1;
            }
            function fixIE6FixedPos() {
                var docStyle = document.body.style;
                docStyle.backgroundImage = 'url("about:blank")';
                docStyle.backgroundAttachment = "fixed";
            }
            var bakCssText,
                placeHolder = document.createElement("div"),
                toolbarBox,
                orgTop,
                getPosition = function (element) {
                    var bcr;
                    //trace  IE6下在控制编辑器显隐时可能会报错，catch一下
                    try {
                        bcr = element.getBoundingClientRect();
                    } catch (e) {
                        bcr = { left: 0, top: 0, height: 0, width: 0 };
                    }
                    var rect = {
                        left: Math.round(bcr.left),
                        top: Math.round(bcr.top),
                        height: Math.round(bcr.bottom - bcr.top),
                        width: Math.round(bcr.right - bcr.left),
                    };
                    var doc;
                    while ((doc = element.ownerDocument) !== document && (element = domUtils.getWindow(doc).frameElement)) {
                        bcr = element.getBoundingClientRect();
                        rect.left += bcr.left;
                        rect.top += bcr.top;
                    }
                    rect.bottom = rect.top + rect.height;
                    rect.right = rect.left + rect.width;
                    return rect;
                };
            var isFullScreening = false;
            function setFloating() {
                if (isFullScreening) {
                    return;
                }
                var toobarBoxPos = domUtils.getXY(toolbarBox),
                    origalFloat = domUtils.getComputedStyle(toolbarBox, "position"),
                    origalLeft = domUtils.getComputedStyle(toolbarBox, "left");
                toolbarBox.style.width = toolbarBox.offsetWidth + "px";
                toolbarBox.style.zIndex = me.options.zIndex * 1 + 1;
                toolbarBox.parentNode.insertBefore(placeHolder, toolbarBox);
                if (LteIE6 || (quirks && browser.ie)) {
                    if (toolbarBox.style.position != "absolute") {
                        toolbarBox.style.position = "absolute";
                    }
                    toolbarBox.style.top = (document.body.scrollTop || document.documentElement.scrollTop) - orgTop + topOffset + "px";
                } else {
                    if (toolbarBox.style.position != "fixed") {
                        toolbarBox.style.position = "fixed";
                        toolbarBox.style.top = topOffset + "px";
                        (origalFloat == "absolute" || origalFloat == "relative") && parseFloat(origalLeft) && (toolbarBox.style.left = toobarBoxPos.x + "px");
                    }
                }
            }
            function unsetFloating() {
                if (placeHolder.parentNode) {
                    placeHolder.parentNode.removeChild(placeHolder);
                }
                toolbarBox.style.cssText = bakCssText;
            }

            function updateFloating() {
                var rect3 = getPosition(me.container);
                var offset = me.options.toolbarTopOffset || 0;
                if (rect3.top < 0 && rect3.bottom - toolbarBox.offsetHeight > offset) {
                    setFloating();
                } else {
                    unsetFloating();
                }
            }
            var defer_updateFloating = utils.defer(
                function () {
                    updateFloating();
                },
                browser.ie ? 200 : 100,
                true
            );

            me.addListener("destroy", function () {
                $(window).off("scroll resize", updateFloating);
                me.removeListener("keydown", defer_updateFloating);
            });

            if (checkHasUI(me)) {
                toolbarBox = $(".edui-toolbar", me.container)[0];
                me.addListener("afteruiready", function () {
                    setTimeout(function () {
                        orgTop = $(toolbarBox).offset().top;
                    }, 100);
                });
                bakCssText = toolbarBox.style.cssText;
                placeHolder.style.height = toolbarBox.offsetHeight + "px";
                if (LteIE6) {
                    fixIE6FixedPos();
                }

                $(window).on("scroll resize", updateFloating);
                me.addListener("keydown", defer_updateFloating);
                me.addListener("resize", function () {
                    unsetFloating();
                    placeHolder.style.height = toolbarBox.offsetHeight + "px";
                    updateFloating();
                });

                me.addListener("beforefullscreenchange", function (t, enabled) {
                    if (enabled) {
                        unsetFloating();
                        isFullScreening = enabled;
                    }
                });
                me.addListener("fullscreenchanged", function (t, enabled) {
                    if (!enabled) {
                        updateFloating();
                    }
                    isFullScreening = enabled;
                });
                me.addListener("sourcemodechanged", function (t, enabled) {
                    setTimeout(function () {
                        updateFloating();
                    }, 0);
                });
                me.addListener("clearDoc", function () {
                    setTimeout(function () {
                        updateFloating();
                    }, 0);
                });
            }
        });
    });
    UM.registerUI("source", function (name) {
        var me = this;
        me.addListener("fullscreenchanged", function () {
            me.$container
                .find("textarea")
                .width(me.$body.width() - 10)
                .height(me.$body.height());
        });
        var $btn = $.eduibutton({
            icon: name,
            click: function () {
                me.execCommand(name);
                UM.setTopEditor(me);
            },
            title: this.getLang("labelMap")[name] || "",
        });

        this.addListener("selectionchange", function () {
            var state = this.queryCommandState(name);
            $btn.edui()
                .disabled(state == -1)
                .active(state == 1);
        });
        return $btn;
    });

    UM.registerUI("paragraph fontfamily fontsize", function (name) {
        var me = this,
            label = (me.options.labelMap && me.options.labelMap[name]) || me.getLang("labelMap." + name),
            options = {
                label: label,
                title: label,
                comboboxName: name,
                items: me.options[name] || [],
                itemStyles: [],
                value: [],
                autowidthitem: [],
            },
            $combox = null,
            comboboxWidget = null;
        if (options.items.length == 0) {
            return null;
        }
        switch (name) {
            case "paragraph":
                options = transForParagraph(options);
                break;

            case "fontfamily":
                options = transForFontfamily(options);
                break;

            case "fontsize":
                options = transForFontsize(options);
                break;
        }

        //实例化
        $combox = $.eduibuttoncombobox(options).css("zIndex", me.getOpt("zIndex") + 1);
        comboboxWidget = $combox.edui();

        comboboxWidget
            .on("comboboxselect", function (evt, res) {
                me.execCommand(name, res.value);
            })
            .on("beforeshow", function () {
                if ($combox.parent().length === 0) {
                    $combox.appendTo(me.$container.find(".edui-dialog-container"));
                }
                UM.setTopEditor(me);
            });

        //状态反射
        this.addListener("selectionchange", function (evt) {
            var state = this.queryCommandState(name),
                value = this.queryCommandValue(name);

            //设置按钮状态
            comboboxWidget
                .button()
                .edui()
                .disabled(state == -1)
                .active(state == 1);
            if (value) {
                //设置label
                value = value
                    .replace(/['"]/g, "")
                    .toLowerCase()
                    .split(/['|"]?\s*,\s*[\1]?/);

                comboboxWidget.selectItemByLabel(value);
            }
        });

        return comboboxWidget.button().addClass("edui-combobox");

        /**
         * 宽度自适应工具函数
         * @param word 单词内容
         * @param hasSuffix 是否含有后缀
         */
        function wordCountAdaptive(word, hasSuffix) {
            var $tmpNode = $("<span>")
                    .html(word)
                    .css({
                        display: "inline",
                        position: "absolute",
                        top: -10000000,
                        left: -100000,
                    })
                    .appendTo(document.body),
                width = $tmpNode.width();

            $tmpNode.remove();
            $tmpNode = null;

            if (width < 50) {
                return word;
            } else {
                word = word.slice(0, hasSuffix ? -4 : -1);

                if (!word.length) {
                    return "...";
                }

                return wordCountAdaptive(word + "...", true);
            }
        }

        //段落参数转换
        function transForParagraph(options) {
            var tempItems = [];

            for (var key in options.items) {
                options.value.push(key);
                tempItems.push(key);
                options.autowidthitem.push(wordCountAdaptive(key));
            }

            options.items = tempItems;
            options.autoRecord = false;

            return options;
        }

        //字体参数转换
        function transForFontfamily(options) {
            var temp = null,
                tempItems = [];

            for (var i = 0, len = options.items.length; i < len; i++) {
                temp = options.items[i].val;
                tempItems.push(temp.split(/\s*,\s*/)[0]);
                options.itemStyles.push("font-family: " + temp);
                options.value.push(temp);
                options.autowidthitem.push(wordCountAdaptive(tempItems[i]));
            }

            options.items = tempItems;

            return options;
        }

        //字体大小参数转换
        function transForFontsize(options) {
            var temp = null,
                tempItems = [];

            options.itemStyles = [];
            options.value = [];

            for (var i = 0, len = options.items.length; i < len; i++) {
                temp = options.items[i];
                tempItems.push(temp);
                options.itemStyles.push("font-size: " + temp + "px");
            }

            options.value = options.items;
            options.items = tempItems;
            options.autoRecord = false;

            return options;
        }
    });

    UM.registerUI("forecolor backcolor", function (name) {
        function getCurrentColor() {
            return domUtils.getComputedStyle($colorLabel[0], "background-color");
        }

        var me = this,
            $colorPickerWidget = null,
            $colorLabel = null,
            $btn = null;

        //querycommand
        this.addListener("selectionchange", function () {
            var state = this.queryCommandState(name);
            $btn.edui()
                .disabled(state == -1)
                .active(state == 1);
        });

        $btn = $.eduicolorsplitbutton({
            icon: name,
            caret: true,
            name: name,
            title: me.getLang("labelMap")[name],
            click: function () {
                me.execCommand(name, getCurrentColor());
            },
        });

        $colorLabel = $btn.edui().colorLabel();

        $colorPickerWidget = $.eduicolorpicker({
            name: name,
            lang_clearColor: me.getLang("clearColor") || "",
            lang_themeColor: me.getLang("themeColor") || "",
            lang_standardColor: me.getLang("standardColor") || "",
        })
            .on("pickcolor", function (evt, color) {
                window.setTimeout(function () {
                    $colorLabel.css("backgroundColor", color);
                    me.execCommand(name, color);
                }, 0);
            })
            .on("show", function () {
                UM.setActiveWidget(colorPickerWidget.root());
            })
            .css("zIndex", me.getOpt("zIndex") + 1);

        $btn.edui()
            .on("arrowclick", function () {
                if (!$colorPickerWidget.parent().length) {
                    me.$container.find(".edui-dialog-container").append($colorPickerWidget);
                }
                $colorPickerWidget.edui().show($btn, {
                    caretDir: "down",
                    offsetTop: -5,
                    offsetLeft: 8,
                    caretLeft: 11,
                    caretTop: -8,
                });
                UM.setTopEditor(me);
            })
            .register("click", $btn, function () {
                $colorPickerWidget.edui().hide();
            });

        return $btn;
    });
})(jQuery);
